28 |
29 |
--------------------------------------------------------------------------------
/.idea/vcs.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2022 Indra Mahkota
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # ModularDynamicFeatureHilt
2 |
3 | An Android template project following a multi module approach with clean architecture. It has been built following Clean Architecture Principle, Repository Pattern, MVVM Architecture in the presentation layer as well as jetpack components.
4 |
5 | I created this repository to demonstrate best development practices by utilizing up to date tech-stack.
6 |
7 | [](https://github.com/mbobiosio/ModularDynamicFeatureHilt/actions?query=workflow%3ABuild)
8 |
9 | [](LICENSE)
10 | [](https://ktlint.github.io/)
11 | 
12 |
13 | ### Details
14 | - **Operating System** : Android
15 | - **Programming Language**: [Kotlin](https://kotlinlang.org)
16 | - **Architecture** : [MVVM and Data Binding](https://developer.android.com/jetpack/guide)
17 | - **Dependency Injection** : [Hilt](https://dagger.dev/hilt/)
18 | - **Fragment Management** : [Navigation Component](https://developer.android.com/guide/navigation/navigation-getting-started)
19 | - **Design** : [Material Design 3](https://m3.material.io)
20 |
21 | ## Prerequisite.
22 |
23 | 1. Android Studio : Arctic Fox | 2020.3.1 3.1 or higher
24 | 2. Android Emulator or Physical android device
25 |
26 | ## Disclaimer.
27 |
28 | - Complex architectures like the pure clean architecture can also increase code complexity since decoupling your code also means creating lots of data transformations(mappers) and models,that may end up increasing the learning curve of your code to a point where it would be better to use a simpler architecture like MVVM.
29 |
30 | - When using dynamic delivery you'll need a PlayStore Developer Account in order to test the dynamic delivery feature However, there is a work around by using [GloballyDynamic](https://globallydynamic.io/) which provides the same dynamic delivery capabilities as PlayStore with other added advantages well suited for testing. [Read More](https://proandroiddev.com/globallydynamic-dynamic-delivery-during-development-f28093ed184f).
31 |
32 | - Dynamic feature modules require use of Android App Bundles which at the moment are not supported by all app distribution platforms and the platforms that support app bundles have different integrations. However, this can be solved by using [GloballyDynamic](https://globallydynamic.io/).
33 |
34 | So let's get started ...
35 |
36 | ## App Structure
37 | ### Dynamic Feature Modules and Dynamic Delivery?
38 |
39 | `Dynamic feature modules` allow separation of certain features and resources from the base module of the app and include them in the app bundle. User can then download and install these modules later when they are required(on demand) even after the app has already been installed.
40 |
41 | `Dynamic Delivery` is Google Play's app serving model that uses [Android App Bundles](https://developer.android.com/guide/app-bundle) to generate and server optimized APKs for each user's device configuration so that users download only the feature and resources the need to run the app.
42 |
43 | Play Feature Delivery allow certain features of the app to be delivered conditionally (depending on user's language, location/country, paying or free user etc.) or downloaded on demand.
44 |
45 | ## Architecture.
46 |
47 | ### What is Clean Architecture?
48 |
49 | A well planned architecture is extremely important for an app to scale and all architectures have one common goal- to manage complexity of your app. This isn't something to be worried about in smaller apps however it may prove very useful when working on apps with longer development lifecycle and a bigger team.
50 |
51 | Clean architecture was proposed by [Robert C. Martin](https://en.wikipedia.org/wiki/Robert_C._Martin) in 2012 in the [Clean Code Blog](http://blog.cleancoder.com/uncle-bob/2012/08/13/the-clean-architecture.html) and it follow the SOLID principle.
52 |
53 |
54 |
55 | The circles represent different layers of your app. Note that:
56 |
57 | - The center circle is the most abstract, and the outer circle is the most concrete. This is called the [Abstraction Principle](https://en.wikipedia.org/wiki/Abstraction_principle_(computer_programming)). The Abstraction Principle specifies that inner circles should contain business logic, and outer circles should contain implementation details.
58 |
59 | - Another principle of Clean Architecture is the [Dependency Inversion](https://en.wikipedia.org/wiki/Dependency_inversion_principle). This rule specifies that each circle can depend only on the nearest inward circle ie. low-level modules do not depend on high-level modules but the other way around.
60 |
61 | ### Why Clean Architecture?
62 |
63 | - Loose coupling between the code - The code can easily be modified without affecting any or a large part of the app's codebase.
64 | - Easier to test code.
65 | - Separation of Concern - Different modules have specific responsibilities making it easier for modification and maintenance.
66 |
67 | ### S.O.L.I.D Principles.
68 |
69 | - [__Single Responsibility__](https://en.wikipedia.org/wiki/Single-responsibility_principle): Each software component should have only one reason to change – one responsibility.
70 |
71 | - [__Open-Closed__](https://en.wikipedia.org/wiki/Open%E2%80%93closed_principle#:~:text=In%20object%2Doriented%20programming%2C%20the,without%20modifying%20its%20source%20code.): You should be able to extend the behavior of a component, without breaking its usage, or modifying its extensions.
72 |
73 | - [__Liskov Substitution__](https://en.wikipedia.org/wiki/Liskov_substitution_principle): If you have a class of one type, and any subclasses of that class, you should be able to represent the base class usage with the subclass, without breaking the app.
74 |
75 | - [__Interface Segregation__](https://en.wikipedia.org/wiki/Interface_segregation_principle): It’s better to have many smaller interfaces than a large one, to prevent the class from implementing the methods that it doesn’t need.
76 |
77 | - [__Dependency Inversion__](https://en.wikipedia.org/wiki/Dependency_inversion_principle): Components should depend on abstractions rather than concrete implementations. Also higher level modules shouldn’t depend on lower level modules.
78 |
79 | ### Gradle Setup
80 |
81 | - [GitHub Actions](https://github.com/mbobiosio/ModularDynamicFeatureHilt/actions) - GitHub actions is used in this project to check for syntax correctness using KtLint, execute the unit tests and generate a new package when pushing changes to the main branch.
82 | - [KtLint](https://github.com/pinterest/ktlint) - The project uses KtLint to check for syntax correctness.
83 | - [Detekt](https://github.com/detekt/detekt) - The project uses Detekt for Kotlin Static Analysis.
84 |
85 | ## Getting started
86 |
87 | There are a few ways to open this project.
88 |
89 | ### Android Studio
90 |
91 | 1. `Android Studio` -> `File` -> `New` -> `From Version control` -> `Git`
92 | 2. Enter `git@github.com:mbobiosio/ModularDynamicFeatureHilt.git` into URL field an press `Clone` button
93 | 3, Build the project and run on an android device or emulator
94 |
95 | ### Command-line + Android Studio
96 |
97 | 1. Run `git clone git@github.com:mbobiosio/ModularDynamicFeatureHilt.git` command to clone project
98 | 2. Open `Android Studio` and select `File | Open...` from the menu. Select cloned directory and press `Open` button
99 | 3. Build the project and run on an android device or emulator
100 |
101 | ### Conclusion
102 |
103 | This project is designed to be a barebone template for new apps.
104 | This project will continuously receive updates to improve overall codebase and other libraries and techniques to keep it up to date.
105 |
106 | ### Screenshots
107 |
108 |
109 |
110 | ### Author
111 |
112 | * [Mbuodile Obiosio](https://www.linkedin.com/in/mb-obiosio/)
113 | * [](https://twitter.com/cazewonder)
114 |
115 | ## 📝 License
116 | This project is released under the MIT license.
117 | See [LICENSE](./LICENSE) for details.
118 |
119 | ```
120 | MIT License
121 |
122 | Copyright (c) 2022 Mbuodile Obiosio
123 |
124 | Permission is hereby granted, free of charge, to any person obtaining a copy
125 | of this software and associated documentation files (the "Software"), to deal
126 | in the Software without restriction, including without limitation the rights
127 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
128 | copies of the Software, and to permit persons to whom the Software is
129 | furnished to do so, subject to the following conditions:
130 |
131 | The above copyright notice and this permission notice shall be included in all
132 | copies or substantial portions of the Software.
133 |
134 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
135 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
136 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
137 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
138 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
139 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
140 | SOFTWARE.
141 | ```
--------------------------------------------------------------------------------
/app/.gitignore:
--------------------------------------------------------------------------------
1 | /build
--------------------------------------------------------------------------------
/app/build.gradle.kts:
--------------------------------------------------------------------------------
1 | import extensions.androidTestDeps
2 | import extensions.appModuleDeps
3 | import extensions.unitTestDeps
4 |
5 | plugins {
6 | id(Plugins.ANDROID_APPLICATION)
7 | kotlin(Plugins.ANDROID)
8 | kotlin(Plugins.KAPT)
9 | id(Plugins.DAGGER_HILT)
10 | id(Plugins.NAVIGATION_SAFE_ARGS)
11 | id(Plugins.KtLint)
12 | }
13 |
14 | android {
15 | compileSdk = AndroidConfig.COMPILE_SDK
16 |
17 | defaultConfig {
18 | applicationId = AndroidConfig.APPLICATION_ID
19 | minSdk = AndroidConfig.MIN_SDK
20 | targetSdk = AndroidConfig.TARGET_SDK
21 | /*
22 | Retaining versionCode to a fixed number to avoid breaking instant run.
23 | */
24 | versionCode = AndroidConfig.VERSION_CODE
25 | versionName = AndroidConfig.VERSION_NAME
26 |
27 | testInstrumentationRunner = AndroidConfig.TEST_INSTRUMENTATION_RUNNER
28 | vectorDrawables.useSupportLibrary = true
29 | setProperty(
30 | "archivesBaseName",
31 | "$applicationId-v$versionName(${AndroidConfig.versionBuild})"
32 | )
33 | }
34 |
35 | buildTypes {
36 | release {
37 | isMinifyEnabled = false
38 | proguardFiles(
39 | getDefaultProguardFile("proguard-android-optimize.txt"),
40 | "proguard-rules.pro"
41 | )
42 | }
43 | }
44 |
45 | compileOptions {
46 | sourceCompatibility = JavaVersion.VERSION_11
47 | targetCompatibility = JavaVersion.VERSION_11
48 | }
49 |
50 | buildFeatures {
51 | viewBinding = true
52 | }
53 |
54 | dynamicFeatures.apply {
55 | add(DynamicFeature.home)
56 | add(DynamicFeature.favorite)
57 | add(DynamicFeature.account)
58 | }
59 |
60 | sourceSets {
61 | getByName("main").java.srcDir("src/main/kotlin")
62 | getByName("test").java.srcDir("src/test/kotlin")
63 | getByName("androidTest").java.srcDir("src/androidTest/kotlin")
64 | }
65 |
66 | applicationVariants.all {
67 | outputs.all {
68 | val outputImpl = this as com.android.build.gradle.internal.api.ApkVariantOutputImpl
69 | if (!buildType.isDebuggable)
70 | outputImpl.versionCodeOverride = AndroidConfig.versionBuild
71 | }
72 | }
73 | }
74 |
75 | kapt {
76 | arguments {
77 | // Make Hilt share the same definition of Components in tests instead of
78 | // creating a new set of Components per test class.
79 | arg("dagger.hilt.shareTestComponents", "true")
80 | }
81 | }
82 |
83 | dependencies {
84 | // Required dependencies
85 | appModuleDeps()
86 |
87 | // Unit Test Deps
88 | unitTestDeps()
89 |
90 | // Instrumentation Test Deps
91 | androidTestDeps()
92 | }
93 |
--------------------------------------------------------------------------------
/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.kts.kts.kts.kts.
4 | #
5 | # For more details, see
6 | # http://developer.android.com/guide/developing/tools/proguard.html
7 |
8 | # If your project uses WebView with JS, uncomment the following
9 | # and specify the fully qualified class name to the JavaScript interface
10 | # class:
11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
12 | # public *;
13 | #}
14 |
15 | # Uncomment this to preserve the line number information for
16 | # debugging stack traces.
17 | #-keepattributes SourceFile,LineNumberTable
18 |
19 | # If you keep the line number information, uncomment this to
20 | # hide the original source file name.
21 | #-renamesourcefileattribute SourceFile
--------------------------------------------------------------------------------
/app/src/androidTest/java/com/mbobiosio/modularapp/ExampleInstrumentedTest.kt:
--------------------------------------------------------------------------------
1 | package com.mbobiosio.modularapp
2 |
3 | import androidx.test.ext.junit.runners.AndroidJUnit4
4 | import androidx.test.platform.app.InstrumentationRegistry
5 | import org.junit.Assert.* // ktlint-disable no-wildcard-imports
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.mbobiosio.hiltexample", appContext.packageName)
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/app/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
6 |
7 |
8 |
16 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
--------------------------------------------------------------------------------
/app/src/main/java/com/mbobiosio/modularapp/DynamicApp.kt:
--------------------------------------------------------------------------------
1 | package com.mbobiosio.modularapp
2 |
3 | import android.app.Application
4 | import dagger.hilt.android.HiltAndroidApp
5 | import timber.log.Timber
6 |
7 | @HiltAndroidApp
8 | class DynamicApp : Application() {
9 | override fun onCreate() {
10 | super.onCreate()
11 | if (BuildConfig.DEBUG) Timber.plant(Timber.DebugTree())
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/app/src/main/java/com/mbobiosio/modularapp/MainActivity.kt:
--------------------------------------------------------------------------------
1 | package com.mbobiosio.modularapp
2 |
3 | import android.os.Bundle
4 | import android.view.View
5 | import androidx.navigation.findNavController
6 | import androidx.navigation.ui.setupWithNavController
7 | import com.mbobiosio.common.base.BaseBindingActivity
8 | import com.mbobiosio.common.util.NavManager
9 | import com.mbobiosio.modularapp.databinding.ActivityMainBinding
10 | import com.mbobiosio.modularapp.util.navigateSafe
11 | import dagger.hilt.android.AndroidEntryPoint
12 |
13 | @AndroidEntryPoint
14 | class MainActivity : BaseBindingActivity() {
15 | private lateinit var binding: ActivityMainBinding
16 |
17 | private val navController get() = findNavController(R.id.nav_host_container)
18 |
19 | private val navManager by lazy {
20 | NavManager()
21 | }
22 |
23 | override fun setLayout(): View {
24 | binding = ActivityMainBinding.inflate(layoutInflater)
25 | return binding.root
26 | }
27 |
28 | override fun setupUI(savedInstanceState: Bundle?) {
29 |
30 | with(binding) {
31 |
32 | bottomNavigationView.setupWithNavController(navController)
33 | bottomNavigationView.setOnItemReselectedListener { }
34 |
35 | navManager.setOnNavEvent {
36 | val navHostFragment = supportFragmentManager.findFragmentById(R.id.nav_host_container)
37 | val currentFragment = navHostFragment?.childFragmentManager?.fragments?.get(0)
38 |
39 | currentFragment?.navigateSafe(it)
40 | }
41 | }
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/app/src/main/java/com/mbobiosio/modularapp/di/AppModule.kt:
--------------------------------------------------------------------------------
1 | package com.mbobiosio.modularapp.di
2 |
3 | import com.mbobiosio.data.SampleRepositoryImpl
4 | import com.mbobiosio.domain.SampleRepository
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 |
11 | @Module
12 | @InstallIn(SingletonComponent::class)
13 | object AppModule {
14 | @Provides
15 | @Singleton
16 | fun provideSampleRepository(): SampleRepository = SampleRepositoryImpl()
17 | }
18 |
--------------------------------------------------------------------------------
/app/src/main/java/com/mbobiosio/modularapp/di/DynamicFeatureDependencies.kt:
--------------------------------------------------------------------------------
1 | package com.mbobiosio.modularapp.di
2 |
3 | import com.mbobiosio.domain.SampleRepository
4 | import dagger.hilt.EntryPoint
5 | import dagger.hilt.InstallIn
6 | import dagger.hilt.components.SingletonComponent
7 |
8 | @EntryPoint
9 | @InstallIn(SingletonComponent::class)
10 | interface DynamicFeatureDependencies {
11 | fun sampleRepository(): SampleRepository
12 | }
13 |
--------------------------------------------------------------------------------
/app/src/main/java/com/mbobiosio/modularapp/di/ViewModelFactory.kt:
--------------------------------------------------------------------------------
1 | package com.mbobiosio.modularapp.di
2 |
3 | import androidx.lifecycle.ViewModel
4 | import androidx.lifecycle.ViewModelProvider
5 | import javax.inject.Inject
6 | import javax.inject.Provider
7 |
8 | @Suppress("UNCHECKED_CAST")
9 | class ViewModelFactory @Inject constructor(
10 | private val viewModels: MutableMap, Provider>
11 | ) : ViewModelProvider.Factory {
12 | override fun create(modelClass: Class): T =
13 | viewModels[modelClass]?.get() as T
14 | }
15 |
--------------------------------------------------------------------------------
/app/src/main/java/com/mbobiosio/modularapp/di/ViewModelKey.kt:
--------------------------------------------------------------------------------
1 | package com.mbobiosio.modularapp.di
2 |
3 | import androidx.lifecycle.ViewModel
4 | import dagger.MapKey
5 | import kotlin.reflect.KClass
6 |
7 | @Target(AnnotationTarget.FUNCTION)
8 | @Retention(AnnotationRetention.RUNTIME)
9 | @MapKey
10 | annotation class ViewModelKey(val value: KClass)
11 |
--------------------------------------------------------------------------------
/app/src/main/java/com/mbobiosio/modularapp/di/ViewModelModule.kt:
--------------------------------------------------------------------------------
1 | package com.mbobiosio.modularapp.di
2 |
3 | import androidx.lifecycle.ViewModelProvider
4 | import dagger.Binds
5 | import dagger.Module
6 | import dagger.hilt.InstallIn
7 | import dagger.hilt.components.SingletonComponent
8 |
9 | /* ViewModel Map needed by ViewModelFactory */
10 | @Module
11 | @InstallIn(SingletonComponent::class)
12 | abstract class ViewModelModule {
13 | @Binds
14 | internal abstract fun bindViewModelFactory(factory: ViewModelFactory): ViewModelProvider.Factory
15 | }
16 |
--------------------------------------------------------------------------------
/app/src/main/java/com/mbobiosio/modularapp/util/FragmentExt.kt:
--------------------------------------------------------------------------------
1 | package com.mbobiosio.modularapp.util
2 |
3 | import androidx.fragment.app.Fragment
4 | import androidx.navigation.NavDirections
5 | import androidx.navigation.NavOptions
6 | import androidx.navigation.fragment.findNavController
7 | import com.mbobiosio.modularapp.R
8 | import timber.log.Timber
9 |
10 | /**
11 | * When trying to navigate to a destination that is not included in the current navigation graph
12 | * the app will crash:
13 | * java.lang.IllegalArgumentException: Navigation action/destination X cannot be found
14 | *
15 | * This happens when a (second) navigation request is triggered from a fragment that is no longer the
16 | * current
17 | * location (currentDestination) in the navController. In other words, when navigating from A to B, there is
18 | * a moment when:
19 | * - Fragment A is still active and showing.
20 | * - The navigation library already changed its current location,currentDestination, to Fragment B.
21 | *
22 | * Crash happens when a second request to navigate from fragment A comes in at exactly this moment (usually
23 | * due
24 | * to extremely fast clicking or multitouch), and it uses a destination that is not included in B’s graph.
25 | * More: https://medium.com/@ffvanderlaan/fixing-the-dreaded-is-unknown-to-this-navcontroller-68c4003824ce
26 | *
27 | * This method navigates only if this is safely possible; when this Fragment is still the current
28 | * destination.
29 | */
30 | fun Fragment.navigateSafe(directions: NavDirections, navOptions: NavOptions? = null) {
31 | if (canNavigate()) findNavController().navigate(directions, navOptions)
32 | }
33 |
34 | /**
35 | * Returns true if the navigation controller is still pointing at 'this' fragment, or false
36 | * if it already navigated away.
37 | */
38 | fun Fragment.canNavigate(): Boolean {
39 | val navController = findNavController()
40 | val destinationIdInNavController = navController.currentDestination?.id
41 |
42 | // add tag_navigation_destination_id to your res\values\ids.xml so that it's unique:
43 | val destinationIdOfThisFragment = view?.getTag(R.id.tag_navigation_destination_id) ?: destinationIdInNavController
44 |
45 | // check that the navigation graph is still in 'this' fragment, if not then the app already navigated:
46 | return if (destinationIdInNavController == destinationIdOfThisFragment) {
47 | view?.setTag(R.id.tag_navigation_destination_id, destinationIdOfThisFragment)
48 | true
49 | } else {
50 | Timber.d("May not navigate: current destination is not the current fragment.")
51 | false
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/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_account_circle.xml:
--------------------------------------------------------------------------------
1 |
7 |
10 |
11 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_favorite_border.xml:
--------------------------------------------------------------------------------
1 |
7 |
10 |
11 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_home.xml:
--------------------------------------------------------------------------------
1 |
7 |
10 |
11 |
--------------------------------------------------------------------------------
/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 |
6 |
7 |
18 |
19 |
25 |
26 |
--------------------------------------------------------------------------------
/app/src/main/res/menu/bottom_menu.xml:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mbobiosio/ModularDynamicFeatureHilt/5a9caa07258a06596deaa07b32edce32c928eaf3/app/src/main/res/mipmap-hdpi/ic_launcher.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mbobiosio/ModularDynamicFeatureHilt/5a9caa07258a06596deaa07b32edce32c928eaf3/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mbobiosio/ModularDynamicFeatureHilt/5a9caa07258a06596deaa07b32edce32c928eaf3/app/src/main/res/mipmap-mdpi/ic_launcher.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mbobiosio/ModularDynamicFeatureHilt/5a9caa07258a06596deaa07b32edce32c928eaf3/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mbobiosio/ModularDynamicFeatureHilt/5a9caa07258a06596deaa07b32edce32c928eaf3/app/src/main/res/mipmap-xhdpi/ic_launcher.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mbobiosio/ModularDynamicFeatureHilt/5a9caa07258a06596deaa07b32edce32c928eaf3/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mbobiosio/ModularDynamicFeatureHilt/5a9caa07258a06596deaa07b32edce32c928eaf3/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mbobiosio/ModularDynamicFeatureHilt/5a9caa07258a06596deaa07b32edce32c928eaf3/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mbobiosio/ModularDynamicFeatureHilt/5a9caa07258a06596deaa07b32edce32c928eaf3/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mbobiosio/ModularDynamicFeatureHilt/5a9caa07258a06596deaa07b32edce32c928eaf3/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp
--------------------------------------------------------------------------------
/app/src/main/res/navigation/nav_graph.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
13 |
14 |
19 |
20 |
21 |
25 |
26 |
--------------------------------------------------------------------------------
/app/src/main/res/values/ids.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/app/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | ModularApp
3 | Module Title
4 | Hello World from App!
5 | Home
6 | Favorite
7 | Account
8 | Home
9 | Home
10 | Favorite
11 |
--------------------------------------------------------------------------------
/app/src/test/java/com/mbobiosio/modularapp/ExampleUnitTest.kt:
--------------------------------------------------------------------------------
1 | package com.mbobiosio.modularapp
2 |
3 | import org.junit.Assert.* // ktlint-disable no-wildcard-imports
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 |
--------------------------------------------------------------------------------
/assets/account.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mbobiosio/ModularDynamicFeatureHilt/5a9caa07258a06596deaa07b32edce32c928eaf3/assets/account.png
--------------------------------------------------------------------------------
/assets/clean_arch.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mbobiosio/ModularDynamicFeatureHilt/5a9caa07258a06596deaa07b32edce32c928eaf3/assets/clean_arch.png
--------------------------------------------------------------------------------
/assets/favorite.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mbobiosio/ModularDynamicFeatureHilt/5a9caa07258a06596deaa07b32edce32c928eaf3/assets/favorite.png
--------------------------------------------------------------------------------
/assets/home.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mbobiosio/ModularDynamicFeatureHilt/5a9caa07258a06596deaa07b32edce32c928eaf3/assets/home.png
--------------------------------------------------------------------------------
/build.gradle.kts:
--------------------------------------------------------------------------------
1 | import com.github.benmanes.gradle.versions.updates.DependencyUpdatesTask
2 | import extensions.isNonStable
3 |
4 | plugins {
5 | id(Plugins.ANDROID_APPLICATION) version (PluginVersion.AGP) apply false
6 | id(Plugins.ANDROID_LIBRARY) version (PluginVersion.AGP) apply false
7 | kotlin(Plugins.ANDROID) version (PluginVersion.KGP) apply false
8 | id(Plugins.AndroidxNavigation) version (PluginVersion.Navigation) apply false
9 | id(Plugins.Detekt) version (PluginVersion.Detekt)
10 | id(Plugins.KtLint) version (PluginVersion.KtLint)
11 | id(Plugins.BenManesVersions) version (PluginVersion.BenManesVersions)
12 | }
13 |
14 | subprojects {
15 | apply {
16 | plugin(Plugins.Detekt)
17 | plugin(Plugins.KtLint)
18 | }
19 |
20 | repositories {
21 | mavenCentral()
22 | }
23 |
24 | ktlint {
25 | debug.set(false)
26 | version.set(PluginVersion.KtLint)
27 | verbose.set(true)
28 | android.set(false)
29 | outputToConsole.set(true)
30 | ignoreFailures.set(false)
31 | enableExperimentalRules.set(true)
32 | filter {
33 | exclude("**/generated/**")
34 | include("**/kotlin/**")
35 | }
36 | }
37 |
38 | detekt {
39 | config = rootProject.files("config/detekt/detekt.yml")
40 | reports {
41 | html {
42 | enabled = true
43 | destination = file("build/reports/detekt.html")
44 | }
45 | }
46 | }
47 | }
48 |
49 | tasks.register("clean", Delete::class.java) {
50 | delete(rootProject.buildDir)
51 | }
52 |
53 | tasks.withType {
54 | rejectVersionIf {
55 | isNonStable(candidate.version)
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/buildSrc/.gitignore:
--------------------------------------------------------------------------------
1 | /build
--------------------------------------------------------------------------------
/buildSrc/build.gradle.kts:
--------------------------------------------------------------------------------
1 | import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
2 |
3 | plugins {
4 | `kotlin-dsl`
5 | }
6 |
7 | repositories {
8 | google()
9 | mavenCentral()
10 | }
11 |
12 | tasks.withType().configureEach {
13 | kotlinOptions {
14 | jvmTarget = JavaVersion.VERSION_11.toString()
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/buildSrc/src/main/kotlin/Dependencies.kt:
--------------------------------------------------------------------------------
1 | object Analysis {
2 | const val ktlint = "0.43.0"
3 | }
4 |
5 | object Deps {
6 | const val androidGradlePlugin = "com.android.tools.build:gradle:7.1.3"
7 |
8 | object Kotlin {
9 | private const val version = "1.6.21"
10 | const val stdlib = "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$version"
11 | const val gradlePlugin = "org.jetbrains.kotlin:kotlin-gradle-plugin:$version"
12 | const val extensions = "org.jetbrains.kotlin:kotlin-android-extensions:$version"
13 | }
14 |
15 | object Coroutines {
16 | private const val version = "1.6.1"
17 | const val core = "org.jetbrains.kotlinx:kotlinx-coroutines-core:$version"
18 | const val android = "org.jetbrains.kotlinx:kotlinx-coroutines-android:$version"
19 | const val test = "org.jetbrains.kotlinx:kotlinx-coroutines-test:$version"
20 | }
21 |
22 | object Android {
23 | const val material = "com.google.android.material:material:1.7.0-alpha01"
24 | }
25 |
26 | object AndroidX {
27 | const val appcompat = "androidx.appcompat:appcompat:1.4.1"
28 | const val palette = "androidx.palette:palette:1.0.0"
29 | const val coreKtx = "androidx.core:core-ktx:1.7.0"
30 |
31 | object Activity {
32 | const val version = "1.4.0"
33 | const val activityKtx = "androidx.activity:activity-ktx:$version"
34 | }
35 |
36 | object Fragment {
37 | const val version = "1.4.1"
38 | const val fragmentKtx = "androidx.fragment:fragment-ktx:$version"
39 | const val fragmentTesting = "androidx.fragment:fragment-testing:$version"
40 | }
41 |
42 | object Constraint {
43 | const val constraintLayout = "androidx.constraintlayout:constraintlayout:2.1.3"
44 | }
45 |
46 | object Lifecycle {
47 | private const val version = "2.4.0"
48 | const val runtime = "androidx.lifecycle:lifecycle-runtime-ktx:$version"
49 | const val viewModel = "androidx.lifecycle:lifecycle-viewmodel-ktx:$version"
50 | const val liveData = "androidx.lifecycle:lifecycle-livedata-ktx:$version"
51 | }
52 |
53 | object Navigation {
54 | private const val version = "2.4.2"
55 | const val navigationSafeArguments =
56 | "androidx.navigation:navigation-safe-args-gradle-plugin:$version"
57 |
58 | const val fragment = "androidx.navigation:navigation-fragment-ktx:$version"
59 | const val ui = "androidx.navigation:navigation-ui-ktx:$version"
60 | const val commonKtx = "androidx.navigation:navigation-common-ktx:$version"
61 | const val dynamicFeaturesFragment =
62 | "androidx.navigation:navigation-dynamic-features-fragment:$version"
63 | }
64 |
65 | object Work {
66 | private const val version = "2.7.1"
67 | const val runtime = "androidx.work:work-runtime:$version"
68 | }
69 |
70 | object Room {
71 | private const val version = "2.4.0"
72 | const val runtime = "androidx.room:room-runtime:$version"
73 | const val ktx = "androidx.room:room-ktx:$version"
74 | const val compiler = "androidx.room:room-compiler:$version"
75 | }
76 | }
77 |
78 | object Dagger {
79 | private const val version = "2.41"
80 | const val hiltAndroid = "com.google.dagger:hilt-android:$version"
81 | const val hiltAndroidCompiler = "com.google.dagger:hilt-compiler:$version"
82 | }
83 |
84 | object OkHttp {
85 | private const val version = "4.9.1"
86 | const val okhttp = "com.squareup.okhttp3:okhttp:$version"
87 | const val logging = "com.squareup.okhttp3:logging-interceptor:$version"
88 | }
89 |
90 | object Retrofit {
91 | private const val version = "2.9.0"
92 | const val retrofit = "com.squareup.retrofit2:retrofit:$version"
93 | const val gsonConverter = "com.squareup.retrofit2:converter-gson:$version"
94 | }
95 |
96 | object Timber {
97 | private const val version = "5.0.1"
98 | const val timber = "com.jakewharton.timber:timber:$version"
99 | }
100 |
101 | object Glide {
102 | private const val version = "4.12.0"
103 | const val glide = "com.github.bumptech.glide:glide:$version"
104 | const val compiler = "com.github.bumptech.glide:compiler:$version"
105 | }
106 | }
107 |
108 | object TestDeps {
109 | object AndroidX {
110 | private const val version = "1.4.0"
111 |
112 | // AndroidX Test - JVM Testing
113 | const val coreKtx = "androidx.test:core-ktx:$version"
114 | const val rules = "androidx.test:rules:$version"
115 | const val coreTesting = "androidx.arch.core:core-testing:2.1.0"
116 | const val androidX_jUnit = "androidx.test.ext:junit-ktx:1.1.3"
117 | const val navigationTest =
118 | "androidx.navigation:navigation-testing:2.4.2"
119 | }
120 |
121 | object Coroutines {
122 | private const val version = "1.3.7"
123 | const val coroutines = "org.jetbrains.kotlinx:kotlinx-coroutines-test:$version"
124 | }
125 |
126 | object JUnit {
127 | private const val version = "4.13.2"
128 | const val junit = "junit:junit:$version"
129 | }
130 |
131 | object MockWebServer {
132 | private const val version = "4.9.3"
133 | const val mockwebserver = "com.squareup.okhttp3:mockwebserver:$version"
134 | const val okhttpIdlingResource = "com.jakewharton.espresso:okhttp3-idling-resource:1.0.0"
135 | }
136 |
137 | object MockK {
138 | const val mockK = "io.mockk:mockk:1.10.0"
139 | }
140 |
141 | object Mockito {
142 | private const val version = "4.3.0"
143 | const val core = "org.mockito:mockito-core:$version"
144 | const val inline = "org.mockito:mockito-inline:$version"
145 | const val android = "org.mockito:mockito-android:$version"
146 | }
147 |
148 | object RoboElectric {
149 | private const val version = "4.6"
150 | const val robolectric = "org.robolectric:robolectric:$version"
151 | }
152 |
153 | object Turbine {
154 | private const val version = "0.7.0"
155 | const val turbine = "app.cash.turbine:turbine:$version"
156 | }
157 |
158 | const val truth = "com.google.truth:truth:1.0.1"
159 | const val espressoCore = "androidx.test.espresso:espresso-core:3.4.0"
160 | }
161 |
--------------------------------------------------------------------------------
/buildSrc/src/main/kotlin/Modules.kt:
--------------------------------------------------------------------------------
1 | object Modules {
2 | const val app = ":app"
3 | const val data = ":data"
4 | const val common = ":common"
5 | const val domain = ":domain"
6 | }
7 |
8 | /*
9 | * Dynamic Feature Modules
10 | * */
11 | object DynamicFeature {
12 | const val home = ":features:home"
13 | const val favorite = ":features:favorite"
14 | const val account = ":features:account"
15 | }
16 |
--------------------------------------------------------------------------------
/buildSrc/src/main/kotlin/Plugins.kt:
--------------------------------------------------------------------------------
1 | object Plugins {
2 |
3 | /*
4 | * Module Level
5 | */
6 | const val ANDROID_APPLICATION = "com.android.application"
7 | const val ANDROID = "android"
8 | const val KAPT = "kapt"
9 | const val ANDROID_LIBRARY = "com.android.library"
10 | const val DAGGER_HILT = "dagger.hilt.android.plugin"
11 | const val ANDROID_DYNAMIC_FEATURE = "com.android.dynamic-feature"
12 | const val NAVIGATION_SAFE_ARGS = "androidx.navigation.safeargs.kotlin"
13 | const val AndroidxNavigation = "androidx.navigation"
14 | const val Detekt = "io.gitlab.arturbosch.detekt"
15 | const val KtLint = "org.jlleitschuh.gradle.ktlint"
16 | const val BenManesVersions = "com.github.ben-manes.versions"
17 | }
18 |
19 | object PluginVersion {
20 | const val AGP = "7.2.1"
21 | const val KGP = "1.6.21"
22 | const val Navigation = "2.4.2"
23 | const val KtLint = "10.3.0"
24 | const val Detekt = "1.20.0"
25 | const val BenManesVersions = "0.42.0"
26 | }
27 |
--------------------------------------------------------------------------------
/buildSrc/src/main/kotlin/Versions.kt:
--------------------------------------------------------------------------------
1 | object Ext {
2 | const val ExtVersion = ""
3 | }
4 |
5 | object AndroidConfig {
6 | const val APPLICATION_ID = "com.mbobiosio.modularapp"
7 | const val MIN_SDK = 21
8 | const val TARGET_SDK = 32
9 | const val COMPILE_SDK = 32
10 |
11 | private const val versionMajor = 1
12 | private const val versionMinor = 0
13 | private const val versionPatch = 0
14 |
15 | const val VERSION_CODE = 1
16 | const val VERSION_NAME = "$versionMajor.$versionMinor.$versionPatch"
17 |
18 | const val TEST_INSTRUMENTATION_RUNNER = "androidx.test.runner.AndroidJUnitRunner"
19 |
20 | /*
21 | Creating a build number based on current date. This is a better option than using build number based
22 | on version number because Google play console needs a build number greater each time a new apk is uploaded.
23 | When using a build number based on version, if your current beta apk is 1.3.0 and you want to publish
24 | an update to your 1.2.0 production version you won't be able to do so. We can't use build number
25 | based on milliseconds time though because of 2100000000 version code limitation. Here we make a
26 | build number that increment only every minute so we should never reach 2100000000.
27 | */
28 | private const val projectStartTimeMillis = 1517443200000
29 | val versionBuild = ((System.currentTimeMillis() - projectStartTimeMillis) / 6000).toInt()
30 | }
31 |
--------------------------------------------------------------------------------
/buildSrc/src/main/kotlin/extensions/DepHandler.kt:
--------------------------------------------------------------------------------
1 | package extensions
2 |
3 | import Deps
4 | import Modules
5 | import TestDeps
6 | import org.gradle.api.artifacts.Dependency
7 | import org.gradle.api.artifacts.ProjectDependency
8 | import org.gradle.api.artifacts.dsl.DependencyHandler
9 |
10 | /*
11 | * Adds required dependencies to app module
12 | * */
13 | fun DependencyHandler.appModuleDeps() {
14 | // Libraries
15 | implementation(project(Modules.common))
16 | implementation(project(Modules.data))
17 | implementation(project(Modules.domain))
18 |
19 | // Navigation Component
20 | implementation(Deps.AndroidX.Navigation.ui)
21 | implementation(Deps.AndroidX.Navigation.fragment)
22 | implementation(Deps.AndroidX.Navigation.dynamicFeaturesFragment)
23 |
24 | api(Deps.Android.material)
25 | api(Deps.AndroidX.appcompat)
26 | api(Deps.AndroidX.Constraint.constraintLayout)
27 |
28 | // Hilt
29 | implementation(Deps.Dagger.hiltAndroid)
30 | kapt(Deps.Dagger.hiltAndroidCompiler)
31 | }
32 |
33 | /*
34 | * Adds required dependencies to home module
35 | * */
36 | fun DependencyHandler.homeModuleDeps() {
37 |
38 | // Libraries
39 | implementation(project(Modules.common))
40 | implementation(project(Modules.app))
41 | implementation(project(Modules.data))
42 | implementation(project(Modules.domain))
43 |
44 | // Navigation components
45 | implementation(Deps.AndroidX.Navigation.fragment)
46 |
47 | // Hilt
48 | implementation(Deps.Dagger.hiltAndroid)
49 | kapt(Deps.Dagger.hiltAndroidCompiler)
50 | }
51 |
52 | /*
53 | * Add required dependencies to favorite module
54 | * */
55 | fun DependencyHandler.favoriteModuleDeps() {
56 | // Libraries
57 | implementation(project(Modules.common))
58 | implementation(project(Modules.app))
59 | implementation(project(Modules.data))
60 | implementation(project(Modules.domain))
61 |
62 | // Navigation components
63 | implementation(Deps.AndroidX.Navigation.fragment)
64 |
65 | // Hilt
66 | implementation(Deps.Dagger.hiltAndroid)
67 | kapt(Deps.Dagger.hiltAndroidCompiler)
68 | }
69 |
70 | /*
71 | * Add required dependencies to account module
72 | * */
73 | fun DependencyHandler.accountModuleDeps() {
74 | // Libraries
75 | implementation(project(Modules.common))
76 | implementation(project(Modules.app))
77 | implementation(project(Modules.data))
78 | implementation(project(Modules.domain))
79 |
80 | // AndroidX
81 | api(Deps.Android.material)
82 | api(Deps.AndroidX.appcompat)
83 | api(Deps.AndroidX.Constraint.constraintLayout)
84 |
85 | // Hilt
86 | implementation(Deps.Dagger.hiltAndroid)
87 | kapt(Deps.Dagger.hiltAndroidCompiler)
88 | }
89 |
90 | /*
91 | * Add required dependencies to common module
92 | * */
93 | fun DependencyHandler.commonModuleDeps() {
94 | // KTX
95 | api(Deps.AndroidX.coreKtx)
96 | implementation(Deps.AndroidX.Fragment.fragmentKtx)
97 | implementation(Deps.AndroidX.Activity.activityKtx)
98 | implementation(Deps.AndroidX.Navigation.commonKtx)
99 |
100 | // Coroutines
101 | api(Deps.Coroutines.core)
102 | api(Deps.Coroutines.android)
103 |
104 | // AndroidX Libs
105 | api(Deps.Android.material)
106 | api(Deps.AndroidX.appcompat)
107 | api(Deps.AndroidX.Constraint.constraintLayout)
108 |
109 | // Timber
110 | api(Deps.Timber.timber)
111 | }
112 |
113 | /*
114 | * Add required dependencies to data module
115 | * */
116 | fun DependencyHandler.dataModuleDeps() {
117 | implementation(project(Modules.common))
118 | implementation(project(Modules.domain))
119 | }
120 |
121 | /*
122 | * Add required dependencies to domain module
123 | * */
124 | fun DependencyHandler.domainModuleDeps() {
125 | implementation(project(Modules.common))
126 | }
127 |
128 | /*
129 | * Add Unit test dependencies
130 | * */
131 | fun DependencyHandler.unitTestDeps() {
132 | // (Required) writing and executing Unit Tests on the JUnit Platform
133 | testImplementation(TestDeps.JUnit.junit)
134 |
135 | // AndroidX Test - JVM testing
136 | testImplementation(TestDeps.AndroidX.coreKtx)
137 |
138 | // Coroutines Test
139 | testImplementation(TestDeps.Coroutines.coroutines)
140 |
141 | // MockWebServer
142 | testImplementation(TestDeps.MockWebServer.mockwebserver)
143 |
144 | // MocKK
145 | testImplementation(TestDeps.MockK.mockK)
146 |
147 | // Truth
148 | testImplementation(TestDeps.truth)
149 | }
150 |
151 | /*
152 | * Add Instrumentation test dependencies
153 | * */
154 | fun DependencyHandler.androidTestDeps() {
155 | // AndroidX Test - Instrumented testing
156 | androidTestImplementation(TestDeps.AndroidX.androidX_jUnit)
157 | androidTestImplementation(TestDeps.AndroidX.coreTesting)
158 |
159 | // Espresso
160 | androidTestImplementation(TestDeps.espressoCore)
161 |
162 | // Navigation Testing
163 | androidTestImplementation(TestDeps.AndroidX.navigationTest)
164 |
165 | // Coroutines Test
166 | androidTestImplementation(TestDeps.Coroutines.coroutines)
167 |
168 | // MockWebServer
169 | androidTestImplementation(TestDeps.MockWebServer.mockwebserver)
170 |
171 | // MockK
172 | androidTestImplementation(TestDeps.MockK.mockK)
173 |
174 | // Truth
175 | androidTestImplementation(TestDeps.truth)
176 | }
177 |
178 | /*
179 | * These extensions mimic the extensions that are generated on the fly by Gradle.
180 | * They are used here to provide above dependency syntax that mimics Gradle Kotlin DSL
181 | * syntax in module\build.gradle.kts files.
182 | */
183 | @Suppress("detekt.UnusedPrivateMember")
184 | private fun DependencyHandler.implementation(dependencyNotation: Any): Dependency? =
185 | add("implementation", dependencyNotation)
186 |
187 | @Suppress("detekt.UnusedPrivateMember")
188 | private fun DependencyHandler.api(dependencyNotation: Any): Dependency? =
189 | add("api", dependencyNotation)
190 |
191 | @Suppress("detekt.UnusedPrivateMember")
192 | private fun DependencyHandler.kapt(dependencyNotation: Any): Dependency? =
193 | add("kapt", dependencyNotation)
194 |
195 | private fun DependencyHandler.testImplementation(dependencyNotation: Any): Dependency? =
196 | add("testImplementation", dependencyNotation)
197 |
198 | private fun DependencyHandler.debugImplementation(dependencyNotation: Any): Dependency? =
199 | add("debugImplementation", dependencyNotation)
200 |
201 | private fun DependencyHandler.testRuntimeOnly(dependencyNotation: Any): Dependency? =
202 | add("testRuntimeOnly", dependencyNotation)
203 |
204 | private fun DependencyHandler.androidTestImplementation(dependencyNotation: Any): Dependency? =
205 | add("androidTestImplementation", dependencyNotation)
206 |
207 | private fun DependencyHandler.project(
208 | path: String,
209 | configuration: String? = null
210 | ): ProjectDependency {
211 | val notation = if (configuration != null) {
212 | mapOf("path" to path, "configuration" to configuration)
213 | } else {
214 | mapOf("path" to path)
215 | }
216 |
217 | return uncheckedCast(project(notation))
218 | }
219 |
220 | @Suppress("unchecked_cast", "nothing_to_inline", "detekt.UnsafeCast")
221 | private inline fun uncheckedCast(obj: Any?): T = obj as T
222 |
--------------------------------------------------------------------------------
/buildSrc/src/main/kotlin/extensions/Extensions.kt:
--------------------------------------------------------------------------------
1 | package extensions
2 |
3 | import java.util.*
4 |
5 | fun String.isStableVersion(): Boolean {
6 | val stableKeyword = listOf("RELEASE", "FINAL", "GA").any { toUpperCase(Locale.ROOT).contains(it) }
7 | return stableKeyword || Regex("^[0-9,.v-]+(-r)?$").matches(this)
8 | }
9 |
10 | fun isNonStable(version: String): Boolean {
11 | val stableKeyword = listOf("RELEASE", "FINAL", "GA").any {
12 | version.toUpperCase(Locale.ROOT).contains(it)
13 | }
14 | val regex = "^[0-9,.v-]+(-r)?$".toRegex()
15 | val isStable = stableKeyword || regex.matches(version)
16 | return isStable.not()
17 | }
18 |
--------------------------------------------------------------------------------
/common/.gitignore:
--------------------------------------------------------------------------------
1 | /build
--------------------------------------------------------------------------------
/common/build.gradle.kts:
--------------------------------------------------------------------------------
1 | import extensions.androidTestDeps
2 | import extensions.commonModuleDeps
3 | import extensions.unitTestDeps
4 |
5 | plugins {
6 | id(Plugins.ANDROID_LIBRARY)
7 | kotlin(Plugins.ANDROID)
8 | }
9 |
10 | android {
11 | compileSdk = AndroidConfig.COMPILE_SDK
12 |
13 | defaultConfig {
14 | minSdk = AndroidConfig.MIN_SDK
15 |
16 | testInstrumentationRunner = AndroidConfig.TEST_INSTRUMENTATION_RUNNER
17 | consumerProguardFiles("consumer-rules.pro")
18 | }
19 |
20 | buildTypes {
21 | release {
22 | isMinifyEnabled = false
23 | proguardFiles(
24 | getDefaultProguardFile("proguard-android-optimize.txt"),
25 | "proguard-rules.pro"
26 | )
27 | }
28 | }
29 | compileOptions {
30 | sourceCompatibility = JavaVersion.VERSION_11
31 | targetCompatibility = JavaVersion.VERSION_11
32 | }
33 | }
34 |
35 | dependencies {
36 | // Required dependencies
37 | commonModuleDeps()
38 |
39 | // Unit Test
40 | unitTestDeps()
41 |
42 | // Android Test
43 | androidTestDeps()
44 | }
45 |
--------------------------------------------------------------------------------
/common/consumer-rules.pro:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mbobiosio/ModularDynamicFeatureHilt/5a9caa07258a06596deaa07b32edce32c928eaf3/common/consumer-rules.pro
--------------------------------------------------------------------------------
/common/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # You can control the set of applied configuration files using the
3 | # proguardFiles setting in build.gradle.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
--------------------------------------------------------------------------------
/common/src/androidTest/java/com/mbobiosio/common/ExampleInstrumentedTest.kt:
--------------------------------------------------------------------------------
1 | package com.mbobiosio.common
2 |
3 | import androidx.test.ext.junit.runners.AndroidJUnit4
4 | import androidx.test.platform.app.InstrumentationRegistry
5 | import org.junit.Assert.* // ktlint-disable no-wildcard-imports
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.mbobiosio.common.test", appContext.packageName)
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/common/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/common/src/main/java/com/mbobiosio/common/base/BaseBindingActivity.kt:
--------------------------------------------------------------------------------
1 | package com.mbobiosio.common.base
2 |
3 | import android.os.Bundle
4 | import android.view.View
5 | import androidx.appcompat.app.AppCompatActivity
6 |
7 | abstract class BaseBindingActivity : AppCompatActivity() {
8 |
9 | protected abstract fun setLayout(): View
10 |
11 | protected abstract fun setupUI(savedInstanceState: Bundle?)
12 |
13 | override fun onCreate(savedInstanceState: Bundle?) {
14 | super.onCreate(savedInstanceState)
15 | setContentView(setLayout())
16 | setupUI(savedInstanceState)
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/common/src/main/java/com/mbobiosio/common/base/BaseBindingFragment.kt:
--------------------------------------------------------------------------------
1 | package com.mbobiosio.common.base
2 |
3 | import android.os.Bundle
4 | import android.view.LayoutInflater
5 | import android.view.View
6 | import android.view.ViewGroup
7 | import androidx.fragment.app.Fragment
8 |
9 | abstract class BaseBindingFragment : Fragment() {
10 |
11 | protected abstract fun bindFragment(inflater: LayoutInflater, container: ViewGroup?): View
12 |
13 | protected abstract fun setupUI(view: View, savedInstanceState: Bundle?)
14 |
15 | protected abstract fun unbindFragment()
16 |
17 | override fun onCreateView(
18 | inflater: LayoutInflater,
19 | container: ViewGroup?,
20 | savedInstanceState: Bundle?
21 | ): View? {
22 | return bindFragment(inflater, container)
23 | }
24 |
25 | override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
26 | super.onViewCreated(view, savedInstanceState)
27 | setupUI(view, savedInstanceState)
28 | }
29 |
30 | override fun onDestroyView() {
31 | super.onDestroyView()
32 | unbindFragment()
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/common/src/main/java/com/mbobiosio/common/util/NavManager.kt:
--------------------------------------------------------------------------------
1 | package com.mbobiosio.common.util
2 |
3 | import androidx.navigation.NavDirections
4 |
5 | /*
6 | * Created by Mbuodile Obiosio on May 05, 2022.
7 | * Twitter: @cazewonder
8 | * Nigeria
9 | */
10 | class NavManager {
11 | private var navEventListener: ((navDirections: NavDirections) -> Unit)? = null
12 |
13 | fun navigate(navDirections: NavDirections) {
14 | navEventListener?.invoke(navDirections)
15 | }
16 |
17 | fun setOnNavEvent(navEventListener: (navDirections: NavDirections) -> Unit) {
18 | this.navEventListener = navEventListener
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/common/src/main/res/values-night/themes.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
31 |
--------------------------------------------------------------------------------
/common/src/main/res/values/colors.xml:
--------------------------------------------------------------------------------
1 |
2 | #8C4380
3 | #FFFFFF
4 | #FFD6F4
5 | #390035
6 | #AB276D
7 | #FFFFFF
8 | #FFD9E7
9 | #3D0021
10 | #006782
11 | #FFFFFF
12 | #B5EAFF
13 | #001F29
14 | #B3261E
15 | #F9DEDC
16 | #FFFFFF
17 | #410E0B
18 | #FFFBFE
19 | #1C1B1F
20 | #FFFBFE
21 | #1C1B1F
22 | #E7E0EC
23 | #49454F
24 | #79747E
25 | #F4EFF4
26 | #313033
27 | #FFACEE
28 | #000000
29 | #FFACEE
30 | #FFACEE
31 | #56124F
32 | #712B67
33 | #FFD6F4
34 | #FFAFD0
35 | #630039
36 | #8B0254
37 | #FFD9E7
38 | #4DD5FF
39 | #003544
40 | #004D62
41 | #B5EAFF
42 | #F2B8B5
43 | #8C1D18
44 | #601410
45 | #F9DEDC
46 | #1C1B1F
47 | #E6E1E5
48 | #1C1B1F
49 | #E6E1E5
50 | #49454F
51 | #CAC4D0
52 | #938F99
53 | #1C1B1F
54 | #E6E1E5
55 | #8C4380
56 | #000000
57 | #8C4380
58 |
59 | #FFFFFF
60 |
--------------------------------------------------------------------------------
/common/src/main/res/values/themes.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
31 |
--------------------------------------------------------------------------------
/common/src/test/java/com/mbobiosio/common/ExampleUnitTest.kt:
--------------------------------------------------------------------------------
1 | package com.mbobiosio.common
2 |
3 | import org.junit.Assert.* // ktlint-disable no-wildcard-imports
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 |
--------------------------------------------------------------------------------
/config/detekt/detekt.yml:
--------------------------------------------------------------------------------
1 | build:
2 | maxIssues: 0
3 | excludeCorrectable: false
4 | weights:
5 | # complexity: 2
6 | # LongParameterList: 1
7 | # style: 1
8 | # comments: 1
9 |
10 | config:
11 | validation: true
12 | warningsAsErrors: false
13 | # when writing own rules with new properties, exclude the property path e.g.: 'my_rule_set,.*>.*>[my_property]'
14 | excludes: ''
15 |
16 | processors:
17 | active: true
18 | exclude:
19 | - 'DetektProgressListener'
20 | # - 'KtFileCountProcessor'
21 | # - 'PackageCountProcessor'
22 | # - 'ClassCountProcessor'
23 | # - 'FunctionCountProcessor'
24 | # - 'PropertyCountProcessor'
25 | # - 'ProjectComplexityProcessor'
26 | # - 'ProjectCognitiveComplexityProcessor'
27 | # - 'ProjectLLOCProcessor'
28 | # - 'ProjectCLOCProcessor'
29 | # - 'ProjectLOCProcessor'
30 | # - 'ProjectSLOCProcessor'
31 | # - 'LicenseHeaderLoaderExtension'
32 |
33 | console-reports:
34 | active: true
35 | exclude:
36 | - 'ProjectStatisticsReport'
37 | - 'ComplexityReport'
38 | - 'NotificationReport'
39 | - 'FindingsReport'
40 | - 'FileBasedFindingsReport'
41 | # - 'LiteFindingsReport'
42 |
43 | output-reports:
44 | active: true
45 | exclude:
46 | # - 'TxtOutputReport'
47 | # - 'XmlOutputReport'
48 | # - 'HtmlOutputReport'
49 |
50 | comments:
51 | active: true
52 | AbsentOrWrongFileLicense:
53 | active: false
54 | licenseTemplateFile: 'license.template'
55 | licenseTemplateIsRegex: false
56 | CommentOverPrivateFunction:
57 | active: false
58 | CommentOverPrivateProperty:
59 | active: false
60 | DeprecatedBlockTag:
61 | active: false
62 | EndOfSentenceFormat:
63 | active: false
64 | endOfSentenceFormat: '([.?!][ \t\n\r\f<])|([.?!:]$)'
65 | OutdatedDocumentation:
66 | active: false
67 | matchTypeParameters: true
68 | matchDeclarationsOrder: true
69 | allowParamOnConstructorProperties: false
70 | UndocumentedPublicClass:
71 | active: false
72 | excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/jsTest/**', '**/iosTest/**']
73 | searchInNestedClass: true
74 | searchInInnerClass: true
75 | searchInInnerObject: true
76 | searchInInnerInterface: true
77 | UndocumentedPublicFunction:
78 | active: false
79 | excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/jsTest/**', '**/iosTest/**']
80 | UndocumentedPublicProperty:
81 | active: false
82 | excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/jsTest/**', '**/iosTest/**']
83 |
84 | complexity:
85 | active: true
86 | ComplexCondition:
87 | active: true
88 | threshold: 4
89 | ComplexInterface:
90 | active: false
91 | threshold: 10
92 | includeStaticDeclarations: false
93 | includePrivateDeclarations: false
94 | ComplexMethod:
95 | active: true
96 | threshold: 15
97 | ignoreSingleWhenExpression: false
98 | ignoreSimpleWhenEntries: false
99 | ignoreNestingFunctions: false
100 | nestingFunctions:
101 | - 'also'
102 | - 'apply'
103 | - 'forEach'
104 | - 'isNotNull'
105 | - 'ifNull'
106 | - 'let'
107 | - 'run'
108 | - 'use'
109 | - 'with'
110 | LabeledExpression:
111 | active: false
112 | ignoredLabels: []
113 | LargeClass:
114 | active: true
115 | threshold: 600
116 | LongMethod:
117 | active: true
118 | threshold: 60
119 | LongParameterList:
120 | active: true
121 | functionThreshold: 6
122 | constructorThreshold: 7
123 | ignoreDefaultParameters: false
124 | ignoreDataClasses: true
125 | ignoreAnnotatedParameter: []
126 | MethodOverloading:
127 | active: false
128 | threshold: 6
129 | NamedArguments:
130 | active: false
131 | threshold: 3
132 | ignoreArgumentsMatchingNames: false
133 | NestedBlockDepth:
134 | active: true
135 | threshold: 4
136 | ReplaceSafeCallChainWithRun:
137 | active: false
138 | StringLiteralDuplication:
139 | active: false
140 | excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/jsTest/**', '**/iosTest/**']
141 | threshold: 3
142 | ignoreAnnotation: true
143 | excludeStringsWithLessThan5Characters: true
144 | ignoreStringsRegex: '$^'
145 | TooManyFunctions:
146 | active: true
147 | excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/jsTest/**', '**/iosTest/**']
148 | thresholdInFiles: 11
149 | thresholdInClasses: 11
150 | thresholdInInterfaces: 11
151 | thresholdInObjects: 11
152 | thresholdInEnums: 11
153 | ignoreDeprecated: false
154 | ignorePrivate: false
155 | ignoreOverridden: false
156 |
157 | coroutines:
158 | active: true
159 | GlobalCoroutineUsage:
160 | active: false
161 | InjectDispatcher:
162 | active: false
163 | dispatcherNames:
164 | - 'IO'
165 | - 'Default'
166 | - 'Unconfined'
167 | RedundantSuspendModifier:
168 | active: false
169 | SleepInsteadOfDelay:
170 | active: false
171 | SuspendFunWithCoroutineScopeReceiver:
172 | active: false
173 | SuspendFunWithFlowReturnType:
174 | active: false
175 |
176 | empty-blocks:
177 | active: true
178 | EmptyCatchBlock:
179 | active: true
180 | allowedExceptionNameRegex: '_|(ignore|expected).*'
181 | EmptyClassBlock:
182 | active: true
183 | EmptyDefaultConstructor:
184 | active: true
185 | EmptyDoWhileBlock:
186 | active: true
187 | EmptyElseBlock:
188 | active: true
189 | EmptyFinallyBlock:
190 | active: true
191 | EmptyForBlock:
192 | active: true
193 | EmptyFunctionBlock:
194 | active: true
195 | ignoreOverridden: false
196 | EmptyIfBlock:
197 | active: true
198 | EmptyInitBlock:
199 | active: true
200 | EmptyKtFile:
201 | active: true
202 | EmptySecondaryConstructor:
203 | active: true
204 | EmptyTryBlock:
205 | active: true
206 | EmptyWhenBlock:
207 | active: true
208 | EmptyWhileBlock:
209 | active: true
210 |
211 | exceptions:
212 | active: true
213 | ExceptionRaisedInUnexpectedLocation:
214 | active: true
215 | methodNames:
216 | - 'equals'
217 | - 'finalize'
218 | - 'hashCode'
219 | - 'toString'
220 | InstanceOfCheckForException:
221 | active: false
222 | excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/jsTest/**', '**/iosTest/**']
223 | NotImplementedDeclaration:
224 | active: false
225 | ObjectExtendsThrowable:
226 | active: false
227 | PrintStackTrace:
228 | active: true
229 | RethrowCaughtException:
230 | active: true
231 | ReturnFromFinally:
232 | active: true
233 | ignoreLabeled: false
234 | SwallowedException:
235 | active: true
236 | ignoredExceptionTypes:
237 | - 'InterruptedException'
238 | - 'MalformedURLException'
239 | - 'NumberFormatException'
240 | - 'ParseException'
241 | allowedExceptionNameRegex: '_|(ignore|expected).*'
242 | ThrowingExceptionFromFinally:
243 | active: true
244 | ThrowingExceptionInMain:
245 | active: false
246 | ThrowingExceptionsWithoutMessageOrCause:
247 | active: true
248 | excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/jsTest/**', '**/iosTest/**']
249 | exceptions:
250 | - 'ArrayIndexOutOfBoundsException'
251 | - 'Exception'
252 | - 'IllegalArgumentException'
253 | - 'IllegalMonitorStateException'
254 | - 'IllegalStateException'
255 | - 'IndexOutOfBoundsException'
256 | - 'NullPointerException'
257 | - 'RuntimeException'
258 | - 'Throwable'
259 | ThrowingNewInstanceOfSameException:
260 | active: true
261 | TooGenericExceptionCaught:
262 | active: true
263 | excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/jsTest/**', '**/iosTest/**']
264 | exceptionNames:
265 | - 'ArrayIndexOutOfBoundsException'
266 | - 'Error'
267 | - 'Exception'
268 | - 'IllegalMonitorStateException'
269 | - 'IndexOutOfBoundsException'
270 | - 'NullPointerException'
271 | - 'RuntimeException'
272 | - 'Throwable'
273 | allowedExceptionNameRegex: '_|(ignore|expected).*'
274 | TooGenericExceptionThrown:
275 | active: true
276 | exceptionNames:
277 | - 'Error'
278 | - 'Exception'
279 | - 'RuntimeException'
280 | - 'Throwable'
281 |
282 | naming:
283 | active: true
284 | BooleanPropertyNaming:
285 | active: false
286 | allowedPattern: '^(is|has|are)'
287 | ignoreOverridden: true
288 | ClassNaming:
289 | active: true
290 | classPattern: '[A-Z][a-zA-Z0-9]*'
291 | ConstructorParameterNaming:
292 | active: true
293 | parameterPattern: '[a-z][A-Za-z0-9]*'
294 | privateParameterPattern: '[a-z][A-Za-z0-9]*'
295 | excludeClassPattern: '$^'
296 | ignoreOverridden: true
297 | EnumNaming:
298 | active: true
299 | enumEntryPattern: '[A-Z][_a-zA-Z0-9]*'
300 | ForbiddenClassName:
301 | active: false
302 | forbiddenName: []
303 | FunctionMaxLength:
304 | active: false
305 | maximumFunctionNameLength: 30
306 | FunctionMinLength:
307 | active: false
308 | minimumFunctionNameLength: 3
309 | FunctionNaming:
310 | active: true
311 | excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/jsTest/**', '**/iosTest/**']
312 | functionPattern: '[a-z][a-zA-Z0-9]*'
313 | excludeClassPattern: '$^'
314 | ignoreOverridden: true
315 | FunctionParameterNaming:
316 | active: true
317 | parameterPattern: '[a-z][A-Za-z0-9]*'
318 | excludeClassPattern: '$^'
319 | ignoreOverridden: true
320 | InvalidPackageDeclaration:
321 | active: false
322 | rootPackage: ''
323 | requireRootInDeclaration: false
324 | LambdaParameterNaming:
325 | active: false
326 | parameterPattern: '[a-z][A-Za-z0-9]*|_'
327 | MatchingDeclarationName:
328 | active: true
329 | mustBeFirst: true
330 | MemberNameEqualsClassName:
331 | active: true
332 | ignoreOverridden: true
333 | NoNameShadowing:
334 | active: false
335 | NonBooleanPropertyPrefixedWithIs:
336 | active: false
337 | ObjectPropertyNaming:
338 | active: true
339 | constantPattern: '[A-Za-z][_A-Za-z0-9]*'
340 | propertyPattern: '[A-Za-z][_A-Za-z0-9]*'
341 | privatePropertyPattern: '(_)?[A-Za-z][_A-Za-z0-9]*'
342 | PackageNaming:
343 | active: true
344 | packagePattern: '[a-z]+(\.[a-z][A-Za-z0-9]*)*'
345 | TopLevelPropertyNaming:
346 | active: true
347 | constantPattern: '[A-Z][_A-Z0-9]*'
348 | propertyPattern: '[A-Za-z][_A-Za-z0-9]*'
349 | privatePropertyPattern: '_?[A-Za-z][_A-Za-z0-9]*'
350 | VariableMaxLength:
351 | active: false
352 | maximumVariableNameLength: 64
353 | VariableMinLength:
354 | active: false
355 | minimumVariableNameLength: 1
356 | VariableNaming:
357 | active: true
358 | variablePattern: '[a-z][A-Za-z0-9]*'
359 | privateVariablePattern: '(_)?[a-z][A-Za-z0-9]*'
360 | excludeClassPattern: '$^'
361 | ignoreOverridden: true
362 |
363 | performance:
364 | active: true
365 | ArrayPrimitive:
366 | active: true
367 | ForEachOnRange:
368 | active: true
369 | excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/jsTest/**', '**/iosTest/**']
370 | SpreadOperator:
371 | active: true
372 | excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/jsTest/**', '**/iosTest/**']
373 | UnnecessaryTemporaryInstantiation:
374 | active: true
375 |
376 | potential-bugs:
377 | active: true
378 | AvoidReferentialEquality:
379 | active: false
380 | forbiddenTypePatterns:
381 | - 'kotlin.String'
382 | CastToNullableType:
383 | active: false
384 | Deprecation:
385 | active: false
386 | DontDowncastCollectionTypes:
387 | active: false
388 | DoubleMutabilityForCollection:
389 | active: false
390 | mutableTypes:
391 | - 'kotlin.collections.MutableList'
392 | - 'kotlin.collections.MutableMap'
393 | - 'kotlin.collections.MutableSet'
394 | - 'java.util.ArrayList'
395 | - 'java.util.LinkedHashSet'
396 | - 'java.util.HashSet'
397 | - 'java.util.LinkedHashMap'
398 | - 'java.util.HashMap'
399 | DuplicateCaseInWhenExpression:
400 | active: true
401 | ElseCaseInsteadOfExhaustiveWhen:
402 | active: false
403 | EqualsAlwaysReturnsTrueOrFalse:
404 | active: true
405 | EqualsWithHashCodeExist:
406 | active: true
407 | ExitOutsideMain:
408 | active: false
409 | ExplicitGarbageCollectionCall:
410 | active: true
411 | HasPlatformType:
412 | active: false
413 | IgnoredReturnValue:
414 | active: false
415 | restrictToAnnotatedMethods: true
416 | returnValueAnnotations:
417 | - '*.CheckResult'
418 | - '*.CheckReturnValue'
419 | ignoreReturnValueAnnotations:
420 | - '*.CanIgnoreReturnValue'
421 | ignoreFunctionCall: []
422 | ImplicitDefaultLocale:
423 | active: true
424 | ImplicitUnitReturnType:
425 | active: false
426 | allowExplicitReturnType: true
427 | InvalidRange:
428 | active: true
429 | IteratorHasNextCallsNextMethod:
430 | active: true
431 | IteratorNotThrowingNoSuchElementException:
432 | active: true
433 | LateinitUsage:
434 | active: false
435 | excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/jsTest/**', '**/iosTest/**']
436 | ignoreOnClassesPattern: ''
437 | MapGetWithNotNullAssertionOperator:
438 | active: false
439 | MissingPackageDeclaration:
440 | active: false
441 | excludes: ['**/*.kts']
442 | MissingWhenCase:
443 | active: true
444 | allowElseExpression: true
445 | NullCheckOnMutableProperty:
446 | active: false
447 | NullableToStringCall:
448 | active: false
449 | RedundantElseInWhen:
450 | active: true
451 | UnconditionalJumpStatementInLoop:
452 | active: false
453 | UnnecessaryNotNullOperator:
454 | active: true
455 | UnnecessarySafeCall:
456 | active: true
457 | UnreachableCatchBlock:
458 | active: false
459 | UnreachableCode:
460 | active: true
461 | UnsafeCallOnNullableType:
462 | active: true
463 | excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/jsTest/**', '**/iosTest/**']
464 | UnsafeCast:
465 | active: true
466 | UnusedUnaryOperator:
467 | active: false
468 | UselessPostfixExpression:
469 | active: false
470 | WrongEqualsTypeParameter:
471 | active: true
472 |
473 | style:
474 | active: true
475 | CanBeNonNullable:
476 | active: false
477 | ClassOrdering:
478 | active: false
479 | CollapsibleIfStatements:
480 | active: false
481 | DataClassContainsFunctions:
482 | active: false
483 | conversionFunctionPrefix: 'to'
484 | DataClassShouldBeImmutable:
485 | active: false
486 | DestructuringDeclarationWithTooManyEntries:
487 | active: false
488 | maxDestructuringEntries: 3
489 | EqualsNullCall:
490 | active: true
491 | EqualsOnSignatureLine:
492 | active: false
493 | ExplicitCollectionElementAccessMethod:
494 | active: false
495 | ExplicitItLambdaParameter:
496 | active: false
497 | ExpressionBodySyntax:
498 | active: false
499 | includeLineWrapping: false
500 | ForbiddenComment:
501 | active: true
502 | values:
503 | - 'FIXME:'
504 | - 'STOPSHIP:'
505 | - 'TODO:'
506 | allowedPatterns: ''
507 | customMessage: ''
508 | ForbiddenImport:
509 | active: false
510 | imports: []
511 | forbiddenPatterns: ''
512 | ForbiddenMethodCall:
513 | active: false
514 | methods:
515 | - 'kotlin.io.print'
516 | - 'kotlin.io.println'
517 | ForbiddenPublicDataClass:
518 | active: true
519 | excludes: ['**']
520 | ignorePackages:
521 | - '*.internal'
522 | - '*.internal.*'
523 | ForbiddenVoid:
524 | active: false
525 | ignoreOverridden: false
526 | ignoreUsageInGenerics: false
527 | FunctionOnlyReturningConstant:
528 | active: true
529 | ignoreOverridableFunction: true
530 | ignoreActualFunction: true
531 | excludedFunctions: ''
532 | LibraryCodeMustSpecifyReturnType:
533 | active: true
534 | excludes: ['**']
535 | LibraryEntitiesShouldNotBePublic:
536 | active: true
537 | excludes: ['**']
538 | LoopWithTooManyJumpStatements:
539 | active: true
540 | maxJumpCount: 1
541 | MagicNumber:
542 | active: true
543 | excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/jsTest/**', '**/iosTest/**']
544 | ignoreNumbers:
545 | - '-1'
546 | - '0'
547 | - '1'
548 | - '2'
549 | ignoreHashCodeFunction: true
550 | ignorePropertyDeclaration: false
551 | ignoreLocalVariableDeclaration: false
552 | ignoreConstantDeclaration: true
553 | ignoreCompanionObjectPropertyDeclaration: true
554 | ignoreAnnotation: false
555 | ignoreNamedArgument: true
556 | ignoreEnums: false
557 | ignoreRanges: false
558 | ignoreExtensionFunctions: true
559 | MandatoryBracesIfStatements:
560 | active: false
561 | MandatoryBracesLoops:
562 | active: false
563 | MaxLineLength:
564 | active: true
565 | maxLineLength: 120
566 | excludePackageStatements: true
567 | excludeImportStatements: true
568 | excludeCommentStatements: false
569 | MayBeConst:
570 | active: true
571 | ModifierOrder:
572 | active: true
573 | MultilineLambdaItParameter:
574 | active: false
575 | NestedClassesVisibility:
576 | active: true
577 | NewLineAtEndOfFile:
578 | active: true
579 | NoTabs:
580 | active: false
581 | ObjectLiteralToLambda:
582 | active: false
583 | OptionalAbstractKeyword:
584 | active: true
585 | OptionalUnit:
586 | active: false
587 | OptionalWhenBraces:
588 | active: false
589 | PreferToOverPairSyntax:
590 | active: false
591 | ProtectedMemberInFinalClass:
592 | active: true
593 | RedundantExplicitType:
594 | active: false
595 | RedundantHigherOrderMapUsage:
596 | active: false
597 | RedundantVisibilityModifierRule:
598 | active: false
599 | ReturnCount:
600 | active: true
601 | max: 2
602 | excludedFunctions: 'equals'
603 | excludeLabeled: false
604 | excludeReturnFromLambda: true
605 | excludeGuardClauses: false
606 | SafeCast:
607 | active: true
608 | SerialVersionUIDInSerializableClass:
609 | active: true
610 | SpacingBetweenPackageAndImports:
611 | active: false
612 | ThrowsCount:
613 | active: true
614 | max: 2
615 | excludeGuardClauses: false
616 | TrailingWhitespace:
617 | active: false
618 | UnderscoresInNumericLiterals:
619 | active: false
620 | acceptableLength: 4
621 | allowNonStandardGrouping: false
622 | UnnecessaryAbstractClass:
623 | active: true
624 | UnnecessaryAnnotationUseSiteTarget:
625 | active: false
626 | UnnecessaryApply:
627 | active: true
628 | UnnecessaryFilter:
629 | active: false
630 | UnnecessaryInheritance:
631 | active: true
632 | UnnecessaryInnerClass:
633 | active: false
634 | UnnecessaryLet:
635 | active: false
636 | UnnecessaryParentheses:
637 | active: false
638 | UntilInsteadOfRangeTo:
639 | active: false
640 | UnusedImports:
641 | active: false
642 | UnusedPrivateClass:
643 | active: true
644 | UnusedPrivateMember:
645 | active: true
646 | allowedNames: '(_|ignored|expected|serialVersionUID)'
647 | UseAnyOrNoneInsteadOfFind:
648 | active: false
649 | UseArrayLiteralsInAnnotations:
650 | active: false
651 | UseCheckNotNull:
652 | active: false
653 | UseCheckOrError:
654 | active: false
655 | UseDataClass:
656 | active: false
657 | allowVars: false
658 | UseEmptyCounterpart:
659 | active: false
660 | UseIfEmptyOrIfBlank:
661 | active: false
662 | UseIfInsteadOfWhen:
663 | active: false
664 | UseIsNullOrEmpty:
665 | active: false
666 | UseOrEmpty:
667 | active: false
668 | UseRequire:
669 | active: false
670 | UseRequireNotNull:
671 | active: false
672 | UselessCallOnNotNull:
673 | active: true
674 | UtilityClassWithPublicConstructor:
675 | active: true
676 | VarCouldBeVal:
677 | active: true
678 | WildcardImport:
679 | active: true
680 | excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/jsTest/**', '**/iosTest/**']
681 | excludeImports:
682 | - 'java.util.*'
683 |
--------------------------------------------------------------------------------
/data/.gitignore:
--------------------------------------------------------------------------------
1 | /build
--------------------------------------------------------------------------------
/data/build.gradle.kts:
--------------------------------------------------------------------------------
1 | import extensions.androidTestDeps
2 | import extensions.dataModuleDeps
3 | import extensions.unitTestDeps
4 |
5 | plugins {
6 | id(Plugins.ANDROID_LIBRARY)
7 | kotlin(Plugins.ANDROID)
8 | }
9 |
10 | android {
11 | compileSdk = AndroidConfig.COMPILE_SDK
12 |
13 | defaultConfig {
14 | minSdk = AndroidConfig.MIN_SDK
15 |
16 | testInstrumentationRunner = AndroidConfig.TEST_INSTRUMENTATION_RUNNER
17 | consumerProguardFiles("consumer-rules.pro")
18 | }
19 |
20 | buildTypes {
21 | release {
22 | isMinifyEnabled = false
23 | proguardFiles(
24 | getDefaultProguardFile("proguard-android-optimize.txt"),
25 | "proguard-rules.pro"
26 | )
27 | }
28 | }
29 |
30 | compileOptions {
31 | sourceCompatibility = JavaVersion.VERSION_11
32 | targetCompatibility = JavaVersion.VERSION_11
33 | }
34 | }
35 |
36 | dependencies {
37 | // Required dependencies
38 | dataModuleDeps()
39 |
40 | // Unit Test
41 | unitTestDeps()
42 |
43 | // Android Test
44 | androidTestDeps()
45 | }
46 |
--------------------------------------------------------------------------------
/data/consumer-rules.pro:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mbobiosio/ModularDynamicFeatureHilt/5a9caa07258a06596deaa07b32edce32c928eaf3/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.kts.kts.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
--------------------------------------------------------------------------------
/data/src/androidTest/java/com/mbobiosio/data/ExampleInstrumentedTest.kt:
--------------------------------------------------------------------------------
1 | package com.mbobiosio.data
2 |
3 | import androidx.test.ext.junit.runners.AndroidJUnit4
4 | import androidx.test.platform.app.InstrumentationRegistry
5 | import org.junit.Assert.* // ktlint-disable no-wildcard-imports
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.mbobiosio.data.test", appContext.packageName)
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/data/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/data/src/main/java/com/mbobiosio/data/SampleRepositoryImpl.kt:
--------------------------------------------------------------------------------
1 | package com.mbobiosio.data
2 |
3 | import com.mbobiosio.domain.SampleRepository
4 |
5 | class SampleRepositoryImpl : SampleRepository {
6 | override fun getDescription() = "Dynamic Feature Fragment: "
7 | }
8 |
--------------------------------------------------------------------------------
/data/src/test/java/com/mbobiosio/data/ExampleUnitTest.kt:
--------------------------------------------------------------------------------
1 | package com.mbobiosio.data
2 |
3 | import org.junit.Assert.* // ktlint-disable no-wildcard-imports
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 |
--------------------------------------------------------------------------------
/domain/.gitignore:
--------------------------------------------------------------------------------
1 | /build
--------------------------------------------------------------------------------
/domain/build.gradle.kts:
--------------------------------------------------------------------------------
1 | import extensions.androidTestDeps
2 | import extensions.domainModuleDeps
3 | import extensions.unitTestDeps
4 |
5 | plugins {
6 | id(Plugins.ANDROID_LIBRARY)
7 | kotlin(Plugins.ANDROID)
8 | }
9 |
10 | android {
11 | compileSdk = AndroidConfig.COMPILE_SDK
12 |
13 | defaultConfig {
14 | minSdk = AndroidConfig.MIN_SDK
15 | targetSdk = AndroidConfig.TARGET_SDK
16 |
17 | testInstrumentationRunner = AndroidConfig.TEST_INSTRUMENTATION_RUNNER
18 | consumerProguardFiles("consumer-rules.pro")
19 | }
20 |
21 | buildTypes {
22 | release {
23 | isMinifyEnabled = false
24 | proguardFiles(
25 | getDefaultProguardFile("proguard-android-optimize.txt"),
26 | "proguard-rules.pro"
27 | )
28 | }
29 | }
30 |
31 | compileOptions {
32 | sourceCompatibility = JavaVersion.VERSION_11
33 | targetCompatibility = JavaVersion.VERSION_11
34 | }
35 | }
36 |
37 | dependencies {
38 | // Required dependencies
39 | domainModuleDeps()
40 |
41 | // Unit Test
42 | unitTestDeps()
43 |
44 | // Android Test
45 | androidTestDeps()
46 | }
47 |
--------------------------------------------------------------------------------
/domain/consumer-rules.pro:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mbobiosio/ModularDynamicFeatureHilt/5a9caa07258a06596deaa07b32edce32c928eaf3/domain/consumer-rules.pro
--------------------------------------------------------------------------------
/domain/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # You can control the set of applied configuration files using the
3 | # proguardFiles setting in build.gradle.kts.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
--------------------------------------------------------------------------------
/domain/src/androidTest/java/com/mbobiosio/domain/ExampleInstrumentedTest.kt:
--------------------------------------------------------------------------------
1 | package com.mbobiosio.domain
2 |
3 | import androidx.test.ext.junit.runners.AndroidJUnit4
4 | import androidx.test.platform.app.InstrumentationRegistry
5 | import org.junit.Assert.* // ktlint-disable no-wildcard-imports
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.mbobiosio.domain.test", appContext.packageName)
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/domain/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/domain/src/main/java/com/mbobiosio/domain/SampleRepository.kt:
--------------------------------------------------------------------------------
1 | package com.mbobiosio.domain
2 |
3 | interface SampleRepository {
4 | fun getDescription(): String
5 | }
6 |
--------------------------------------------------------------------------------
/domain/src/test/java/com/mbobiosio/domain/ExampleUnitTest.kt:
--------------------------------------------------------------------------------
1 | package com.mbobiosio.domain
2 |
3 | import org.junit.Assert.* // ktlint-disable no-wildcard-imports
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/account/.gitignore:
--------------------------------------------------------------------------------
1 | /build
--------------------------------------------------------------------------------
/features/account/build.gradle.kts:
--------------------------------------------------------------------------------
1 | import extensions.accountModuleDeps
2 | import extensions.androidTestDeps
3 | import extensions.unitTestDeps
4 |
5 | plugins {
6 | id(Plugins.ANDROID_DYNAMIC_FEATURE)
7 | kotlin(Plugins.ANDROID)
8 | kotlin(Plugins.KAPT)
9 | id(Plugins.DAGGER_HILT)
10 | }
11 |
12 | android {
13 | compileSdk = AndroidConfig.COMPILE_SDK
14 |
15 | defaultConfig {
16 | minSdk = AndroidConfig.MIN_SDK
17 |
18 | testInstrumentationRunner = AndroidConfig.TEST_INSTRUMENTATION_RUNNER
19 | }
20 |
21 | buildTypes {
22 | release {
23 | isMinifyEnabled = false
24 | proguardFiles(
25 | getDefaultProguardFile("proguard-android-optimize.txt"),
26 | "proguard-rules.pro"
27 | )
28 | }
29 | }
30 |
31 | buildFeatures {
32 | viewBinding = true
33 | // dataBinding = true
34 | }
35 |
36 | kapt {
37 | correctErrorTypes = true
38 | }
39 | }
40 |
41 | kapt {
42 | arguments {
43 | // Make Hilt share the same definition of Components in tests instead of
44 | // creating a new set of Components per test class.
45 | arg("dagger.hilt.shareTestComponents", "true")
46 | }
47 | }
48 |
49 | dependencies {
50 | // Required dependencies
51 | accountModuleDeps()
52 |
53 | // Unit Test
54 | unitTestDeps()
55 |
56 | // Android Test
57 | androidTestDeps()
58 | }
59 |
--------------------------------------------------------------------------------
/features/account/src/androidTest/java/com/mbobiosio/account/ExampleInstrumentedTest.kt:
--------------------------------------------------------------------------------
1 | package com.mbobiosio.account
2 |
3 | import androidx.test.ext.junit.runners.AndroidJUnit4
4 | import androidx.test.platform.app.InstrumentationRegistry
5 | import org.junit.Assert.* // ktlint-disable no-wildcard-imports
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.mbobiosio.account", appContext.packageName)
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/features/account/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/features/account/src/main/java/com/mbobiosio/account/di/DynamicFeatureComponent.kt:
--------------------------------------------------------------------------------
1 | package com.mbobiosio.account.di
2 |
3 | import android.content.Context
4 | import com.mbobiosio.account.presentation.AccountFragment
5 | import com.mbobiosio.modularapp.di.DynamicFeatureDependencies
6 | import dagger.BindsInstance
7 | import dagger.Component
8 | import dagger.hilt.android.EntryPointAccessors
9 |
10 | @Component(
11 | dependencies = [DynamicFeatureDependencies::class],
12 | modules = [
13 | DynamicFeatureModule::class
14 | ]
15 | )
16 | interface DynamicFeatureComponent {
17 | @Component.Factory
18 | interface Factory {
19 | fun create(
20 | @BindsInstance context: Context,
21 | dependencies: DynamicFeatureDependencies
22 | ): DynamicFeatureComponent
23 | }
24 |
25 | fun inject(fragment: AccountFragment)
26 | }
27 |
28 | internal fun AccountFragment.inject() {
29 | DaggerDynamicFeatureComponent.factory().create(
30 | requireContext(),
31 | EntryPointAccessors.fromApplication(
32 | requireContext().applicationContext,
33 | DynamicFeatureDependencies::class.java
34 | )
35 | ).inject(this)
36 | }
37 |
--------------------------------------------------------------------------------
/features/account/src/main/java/com/mbobiosio/account/di/DynamicFeatureModule.kt:
--------------------------------------------------------------------------------
1 | package com.mbobiosio.account.di
2 |
3 | import androidx.lifecycle.ViewModel
4 | import androidx.lifecycle.ViewModelProvider
5 | import com.mbobiosio.account.viewmodel.AccountViewModel
6 | import com.mbobiosio.modularapp.di.ViewModelFactory
7 | import com.mbobiosio.modularapp.di.ViewModelKey
8 | import dagger.Binds
9 | import dagger.Module
10 | import dagger.hilt.InstallIn
11 | import dagger.hilt.android.components.ViewModelComponent
12 | import dagger.multibindings.IntoMap
13 |
14 | @Module
15 | @InstallIn(ViewModelComponent::class)
16 | abstract class DynamicFeatureModule {
17 | @Binds
18 | internal abstract fun bindViewModelFactory(factory: ViewModelFactory): ViewModelProvider.Factory
19 |
20 | @Binds
21 | @IntoMap
22 | @ViewModelKey(AccountViewModel::class)
23 | internal abstract fun bindDynamicFeatureViewModel(viewModel: AccountViewModel): ViewModel
24 | }
25 |
--------------------------------------------------------------------------------
/features/account/src/main/java/com/mbobiosio/account/presentation/AccountFragment.kt:
--------------------------------------------------------------------------------
1 | package com.mbobiosio.account.presentation
2 |
3 | import android.content.Context
4 | import android.os.Bundle
5 | import android.view.LayoutInflater
6 | import android.view.View
7 | import android.view.ViewGroup
8 | import android.widget.Toast
9 | import androidx.lifecycle.ViewModelProvider
10 | import com.mbobiosio.account.R
11 | import com.mbobiosio.account.databinding.FragmentAccountBinding
12 | import com.mbobiosio.account.di.inject
13 | import com.mbobiosio.account.viewmodel.AccountViewModel
14 | import com.mbobiosio.common.base.BaseBindingFragment
15 | import javax.inject.Inject
16 |
17 | class AccountFragment : BaseBindingFragment() {
18 |
19 | private var _binding: FragmentAccountBinding? = null
20 | private val binding get() = _binding!!
21 |
22 | @Inject
23 | lateinit var viewModelFactory: ViewModelProvider.Factory
24 | private val viewModel: AccountViewModel by lazy {
25 | ViewModelProvider(this, viewModelFactory)[AccountViewModel::class.java]
26 | }
27 |
28 | override fun onAttach(context: Context) {
29 | super.onAttach(context)
30 | inject()
31 | }
32 |
33 | override fun bindFragment(inflater: LayoutInflater, container: ViewGroup?): View {
34 | _binding = FragmentAccountBinding.inflate(inflater, container, false)
35 | return binding.root
36 | }
37 |
38 | override fun setupUI(view: View, savedInstanceState: Bundle?) {
39 |
40 | with(binding) {
41 | val description = viewModel.getDescription().plus(getString(R.string.account))
42 | toolBar.title = description
43 |
44 | fab.setOnClickListener {
45 | Toast.makeText(activity, description, Toast.LENGTH_SHORT).show()
46 | }
47 | }
48 | }
49 |
50 | override fun unbindFragment() {
51 | _binding = null
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/features/account/src/main/java/com/mbobiosio/account/viewmodel/AccountViewModel.kt:
--------------------------------------------------------------------------------
1 | package com.mbobiosio.account.viewmodel
2 |
3 | import androidx.lifecycle.ViewModel
4 | import com.mbobiosio.domain.SampleRepository
5 | import javax.inject.Inject
6 |
7 | class AccountViewModel @Inject constructor(private val repository: SampleRepository) : ViewModel() {
8 | fun getDescription() = repository.getDescription()
9 | }
10 |
--------------------------------------------------------------------------------
/features/account/src/main/res/drawable/ic_account_circle.xml:
--------------------------------------------------------------------------------
1 |
7 |
10 |
11 |
--------------------------------------------------------------------------------
/features/account/src/main/res/layout/fragment_account.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
18 |
19 |
30 |
31 |
--------------------------------------------------------------------------------
/features/account/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | Account Fragment
4 |
--------------------------------------------------------------------------------
/features/account/src/test/java/com/mbobiosio/account/ExampleUnitTest.kt:
--------------------------------------------------------------------------------
1 | package com.mbobiosio.account
2 |
3 | import org.junit.Assert.* // ktlint-disable no-wildcard-imports
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/favorite/.gitignore:
--------------------------------------------------------------------------------
1 | /build
--------------------------------------------------------------------------------
/features/favorite/build.gradle.kts:
--------------------------------------------------------------------------------
1 | import extensions.androidTestDeps
2 | import extensions.favoriteModuleDeps
3 | import extensions.unitTestDeps
4 |
5 | plugins {
6 | id(Plugins.ANDROID_DYNAMIC_FEATURE)
7 | kotlin(Plugins.ANDROID)
8 | kotlin(Plugins.KAPT)
9 | id(Plugins.DAGGER_HILT)
10 | }
11 |
12 | android {
13 | compileSdk = AndroidConfig.COMPILE_SDK
14 |
15 | defaultConfig {
16 | minSdk = AndroidConfig.MIN_SDK
17 |
18 | testInstrumentationRunner = AndroidConfig.TEST_INSTRUMENTATION_RUNNER
19 | }
20 |
21 | buildTypes {
22 | release {
23 | isMinifyEnabled = false
24 | proguardFiles(
25 | getDefaultProguardFile("proguard-android-optimize.txt"),
26 | "proguard-rules.pro"
27 | )
28 | }
29 | }
30 |
31 | buildFeatures {
32 | viewBinding = true
33 | // dataBinding = true
34 | }
35 |
36 | kapt {
37 | correctErrorTypes = true
38 | }
39 | }
40 |
41 | kapt {
42 | arguments {
43 | // Make Hilt share the same definition of Components in tests instead of
44 | // creating a new set of Components per test class.
45 | arg("dagger.hilt.shareTestComponents", "true")
46 | }
47 | }
48 |
49 | dependencies {
50 | // Required dependencies
51 | favoriteModuleDeps()
52 |
53 | // Unit Test
54 | unitTestDeps()
55 |
56 | // Android Test
57 | androidTestDeps()
58 | }
59 |
--------------------------------------------------------------------------------
/features/favorite/src/androidTest/java/com/mbobiosio/favorite/ExampleInstrumentedTest.kt:
--------------------------------------------------------------------------------
1 | package com.mbobiosio.favorite
2 |
3 | import androidx.test.ext.junit.runners.AndroidJUnit4
4 | import androidx.test.platform.app.InstrumentationRegistry
5 | import org.junit.Assert.* // ktlint-disable no-wildcard-imports
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.mbobiosio.favorite", appContext.packageName)
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/features/favorite/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/features/favorite/src/main/java/com/mbobiosio/favorite/di/DynamicFeatureComponent.kt:
--------------------------------------------------------------------------------
1 | package com.mbobiosio.favorite.di
2 |
3 | import android.content.Context
4 | import com.mbobiosio.favorite.presentation.FavoriteFragment
5 | import com.mbobiosio.modularapp.di.DynamicFeatureDependencies
6 | import dagger.BindsInstance
7 | import dagger.Component
8 | import dagger.hilt.android.EntryPointAccessors
9 |
10 | @Component(
11 | dependencies = [DynamicFeatureDependencies::class],
12 | modules = [
13 | DynamicFeatureModule::class
14 | ]
15 | )
16 | interface DynamicFeatureComponent {
17 | @Component.Factory
18 | interface Factory {
19 | fun create(
20 | @BindsInstance context: Context,
21 | dependencies: DynamicFeatureDependencies
22 | ): DynamicFeatureComponent
23 | }
24 |
25 | fun inject(fragment: FavoriteFragment)
26 | }
27 |
28 | internal fun FavoriteFragment.inject() {
29 | DaggerDynamicFeatureComponent.factory().create(
30 | requireContext(),
31 | EntryPointAccessors.fromApplication(
32 | requireContext().applicationContext,
33 | DynamicFeatureDependencies::class.java
34 | )
35 | ).inject(this)
36 | }
37 |
--------------------------------------------------------------------------------
/features/favorite/src/main/java/com/mbobiosio/favorite/di/DynamicFeatureModule.kt:
--------------------------------------------------------------------------------
1 | package com.mbobiosio.favorite.di
2 |
3 | import androidx.lifecycle.ViewModel
4 | import androidx.lifecycle.ViewModelProvider
5 | import com.mbobiosio.favorite.viewmodel.FavoriteViewModel
6 | import com.mbobiosio.modularapp.di.ViewModelFactory
7 | import com.mbobiosio.modularapp.di.ViewModelKey
8 | import dagger.Binds
9 | import dagger.Module
10 | import dagger.hilt.InstallIn
11 | import dagger.hilt.android.components.ViewModelComponent
12 | import dagger.multibindings.IntoMap
13 |
14 | @Module
15 | @InstallIn(ViewModelComponent::class)
16 | abstract class DynamicFeatureModule {
17 | @Binds
18 | internal abstract fun bindViewModelFactory(factory: ViewModelFactory): ViewModelProvider.Factory
19 |
20 | @Binds
21 | @IntoMap
22 | @ViewModelKey(FavoriteViewModel::class)
23 | internal abstract fun bindDynamicFeatureViewModel(viewModel: FavoriteViewModel): ViewModel
24 | }
25 |
--------------------------------------------------------------------------------
/features/favorite/src/main/java/com/mbobiosio/favorite/presentation/FavoriteFragment.kt:
--------------------------------------------------------------------------------
1 | package com.mbobiosio.favorite.presentation
2 |
3 | import android.content.Context
4 | import android.os.Bundle
5 | import android.view.LayoutInflater
6 | import android.view.View
7 | import android.view.ViewGroup
8 | import android.widget.Toast
9 | import androidx.lifecycle.ViewModelProvider
10 | import com.mbobiosio.common.base.BaseBindingFragment
11 | import com.mbobiosio.favorite.R
12 | import com.mbobiosio.favorite.databinding.FavoriteFragmentBinding
13 | import com.mbobiosio.favorite.di.inject
14 | import com.mbobiosio.favorite.viewmodel.FavoriteViewModel
15 | import javax.inject.Inject
16 |
17 | class FavoriteFragment : BaseBindingFragment() {
18 |
19 | private var _binding: FavoriteFragmentBinding? = null
20 | private val binding get() = _binding!!
21 |
22 | @Inject
23 | lateinit var viewModelFactory: ViewModelProvider.Factory
24 |
25 | private val viewModel: FavoriteViewModel by lazy {
26 | ViewModelProvider(this, viewModelFactory)[FavoriteViewModel::class.java]
27 | }
28 |
29 | override fun bindFragment(inflater: LayoutInflater, container: ViewGroup?): View {
30 | _binding = FavoriteFragmentBinding.inflate(inflater, container, false)
31 | return binding.root
32 | }
33 |
34 | override fun setupUI(view: View, savedInstanceState: Bundle?) {
35 |
36 | with(binding) {
37 | val description = viewModel.getDescription().plus(getString(R.string.favorite))
38 |
39 | toolBar.title = description
40 | fab.setOnClickListener {
41 | Toast.makeText(activity, description, Toast.LENGTH_SHORT).show()
42 | }
43 | }
44 | }
45 |
46 | override fun unbindFragment() {
47 | _binding = null
48 | }
49 |
50 | override fun onAttach(context: Context) {
51 | super.onAttach(context)
52 | inject()
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/features/favorite/src/main/java/com/mbobiosio/favorite/viewmodel/FavoriteViewModel.kt:
--------------------------------------------------------------------------------
1 | package com.mbobiosio.favorite.viewmodel
2 |
3 | import androidx.lifecycle.ViewModel
4 | import com.mbobiosio.domain.SampleRepository
5 | import javax.inject.Inject
6 |
7 | class FavoriteViewModel @Inject constructor(
8 | private val repository: SampleRepository
9 | ) : ViewModel() {
10 | fun getDescription() = repository.getDescription()
11 | }
12 |
--------------------------------------------------------------------------------
/features/favorite/src/main/res/drawable/ic_favorite_border.xml:
--------------------------------------------------------------------------------
1 |
7 |
10 |
11 |
--------------------------------------------------------------------------------
/features/favorite/src/main/res/layout/favorite_fragment.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
18 |
19 |
30 |
31 |
--------------------------------------------------------------------------------
/features/favorite/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | Favorite Fragment
4 |
--------------------------------------------------------------------------------
/features/favorite/src/test/java/com/mbobiosio/favorite/ExampleUnitTest.kt:
--------------------------------------------------------------------------------
1 | package com.mbobiosio.favorite
2 |
3 | import org.junit.Assert.* // ktlint-disable no-wildcard-imports
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/home/.gitignore:
--------------------------------------------------------------------------------
1 | /build
--------------------------------------------------------------------------------
/features/home/build.gradle.kts:
--------------------------------------------------------------------------------
1 | import extensions.androidTestDeps
2 | import extensions.homeModuleDeps
3 | import extensions.unitTestDeps
4 |
5 | plugins {
6 | id(Plugins.ANDROID_DYNAMIC_FEATURE)
7 | kotlin(Plugins.ANDROID)
8 | kotlin(Plugins.KAPT)
9 | id(Plugins.DAGGER_HILT)
10 | }
11 |
12 | android {
13 | compileSdk = AndroidConfig.COMPILE_SDK
14 |
15 | defaultConfig {
16 | minSdk = AndroidConfig.MIN_SDK
17 |
18 | testInstrumentationRunner = AndroidConfig.TEST_INSTRUMENTATION_RUNNER
19 | }
20 |
21 | buildTypes {
22 | release {
23 | isMinifyEnabled = false
24 | proguardFiles(
25 | getDefaultProguardFile("proguard-android-optimize.txt"),
26 | "proguard-rules.pro"
27 | )
28 | }
29 | }
30 |
31 | buildFeatures {
32 | viewBinding = true
33 | // dataBinding = true
34 | }
35 |
36 | kapt {
37 | correctErrorTypes = true
38 | }
39 | }
40 |
41 | kapt {
42 | arguments {
43 | // Make Hilt share the same definition of Components in tests instead of
44 | // creating a new set of Components per test class.
45 | arg("dagger.hilt.shareTestComponents", "true")
46 | }
47 | }
48 |
49 | dependencies {
50 | // Required dependencies
51 | homeModuleDeps()
52 |
53 | // Unit Test
54 | unitTestDeps()
55 |
56 | // Android Test
57 | androidTestDeps()
58 | }
59 |
--------------------------------------------------------------------------------
/features/home/src/androidTest/java/com/mbobiosio/home/ExampleInstrumentedTest.kt:
--------------------------------------------------------------------------------
1 | package com.mbobiosio.home
2 |
3 | import androidx.test.ext.junit.runners.AndroidJUnit4
4 | import androidx.test.platform.app.InstrumentationRegistry
5 | import org.junit.Assert.* // ktlint-disable no-wildcard-imports
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.mbobiosio.home", appContext.packageName)
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/features/home/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
8 |
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/features/home/src/main/java/com/mbobiosio/home/di/DynamicFeatureComponent.kt:
--------------------------------------------------------------------------------
1 | package com.mbobiosio.home.di
2 |
3 | import android.content.Context
4 | import com.mbobiosio.home.presentation.HomeFragment
5 | import com.mbobiosio.modularapp.di.DynamicFeatureDependencies
6 | import dagger.BindsInstance
7 | import dagger.Component
8 |
9 | @Component(
10 | dependencies = [DynamicFeatureDependencies::class],
11 | modules = [
12 | DynamicFeatureModule::class
13 | ]
14 | )
15 | interface DynamicFeatureComponent {
16 | @Component.Factory
17 | interface Factory {
18 | fun create(
19 | @BindsInstance context: Context,
20 | dependencies: DynamicFeatureDependencies
21 | ): DynamicFeatureComponent
22 | }
23 |
24 | fun inject(fragment: HomeFragment)
25 | }
26 |
27 | internal fun HomeFragment.inject() {
28 | DaggerDynamicFeatureComponent.factory().create(
29 | requireContext(),
30 | dagger.hilt.android.EntryPointAccessors.fromApplication(
31 | requireContext().applicationContext,
32 | DynamicFeatureDependencies::class.java
33 | )
34 | ).inject(this)
35 | }
36 |
--------------------------------------------------------------------------------
/features/home/src/main/java/com/mbobiosio/home/di/DynamicFeatureModule.kt:
--------------------------------------------------------------------------------
1 | package com.mbobiosio.home.di
2 |
3 | import androidx.lifecycle.ViewModel
4 | import androidx.lifecycle.ViewModelProvider
5 | import com.mbobiosio.home.viewmodel.HomeViewModel
6 | import com.mbobiosio.modularapp.di.ViewModelFactory
7 | import com.mbobiosio.modularapp.di.ViewModelKey
8 | import dagger.Binds
9 | import dagger.Module
10 | import dagger.hilt.InstallIn
11 | import dagger.hilt.android.components.ViewModelComponent
12 | import dagger.multibindings.IntoMap
13 |
14 | @Module
15 | @InstallIn(ViewModelComponent::class)
16 | abstract class DynamicFeatureModule {
17 | @Binds
18 | internal abstract fun bindViewModelFactory(factory: ViewModelFactory): ViewModelProvider.Factory
19 |
20 | @Binds
21 | @IntoMap
22 | @ViewModelKey(HomeViewModel::class)
23 | internal abstract fun bindHomeViewModel(viewModel: HomeViewModel): ViewModel
24 | }
25 |
--------------------------------------------------------------------------------
/features/home/src/main/java/com/mbobiosio/home/presentation/HomeFragment.kt:
--------------------------------------------------------------------------------
1 | package com.mbobiosio.home.presentation
2 |
3 | import android.content.Context
4 | import android.os.Bundle
5 | import android.view.LayoutInflater
6 | import android.view.View
7 | import android.view.ViewGroup
8 | import android.widget.Toast
9 | import androidx.lifecycle.ViewModelProvider
10 | import com.mbobiosio.common.base.BaseBindingFragment
11 | import com.mbobiosio.home.R
12 | import com.mbobiosio.home.databinding.FragmentHomeBinding
13 | import com.mbobiosio.home.di.inject
14 | import com.mbobiosio.home.viewmodel.HomeViewModel
15 | import javax.inject.Inject
16 |
17 | class HomeFragment : BaseBindingFragment() {
18 | private var _binding: FragmentHomeBinding? = null
19 | private val binding get() = _binding!!
20 |
21 | @Inject
22 | lateinit var viewModelFactory: ViewModelProvider.Factory
23 |
24 | private val viewModel: HomeViewModel by lazy {
25 | ViewModelProvider(this, viewModelFactory)[HomeViewModel::class.java]
26 | }
27 |
28 | override fun bindFragment(inflater: LayoutInflater, container: ViewGroup?): View {
29 | _binding = FragmentHomeBinding.inflate(inflater, container, false)
30 | return binding.root
31 | }
32 |
33 | override fun setupUI(view: View, savedInstanceState: Bundle?) {
34 | with(binding) {
35 | val description = viewModel.getDescription().plus(getString(R.string.home))
36 |
37 | toolBar.title = description
38 |
39 | fab.setOnClickListener {
40 | Toast.makeText(activity, description, Toast.LENGTH_SHORT).show()
41 | }
42 | }
43 | }
44 |
45 | override fun unbindFragment() {
46 | _binding = null
47 | }
48 |
49 | override fun onAttach(context: Context) {
50 | super.onAttach(context)
51 | inject()
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/features/home/src/main/java/com/mbobiosio/home/viewmodel/HomeViewModel.kt:
--------------------------------------------------------------------------------
1 | package com.mbobiosio.home.viewmodel
2 |
3 | import androidx.lifecycle.ViewModel
4 | import com.mbobiosio.domain.SampleRepository
5 | import javax.inject.Inject
6 |
7 | /**
8 | * @author Mbuodile Obiosio
9 | * Twitter: @cazewonder
10 | * Nigeria
11 | */
12 | class HomeViewModel @Inject constructor(
13 | private val repository: SampleRepository
14 | ) : ViewModel() {
15 |
16 | fun getDescription() = repository.getDescription()
17 | }
18 |
--------------------------------------------------------------------------------
/features/home/src/main/res/drawable/ic_add_home.xml:
--------------------------------------------------------------------------------
1 |
7 |
10 |
13 |
14 |
--------------------------------------------------------------------------------
/features/home/src/main/res/layout/fragment_home.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
18 |
19 |
30 |
31 |
--------------------------------------------------------------------------------
/features/home/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | Home Fragment
4 |
--------------------------------------------------------------------------------
/features/home/src/test/java/com/mbobiosio/home/ExampleUnitTest.kt:
--------------------------------------------------------------------------------
1 | package com.mbobiosio.home
2 |
3 | import org.junit.Assert.* // ktlint-disable no-wildcard-imports
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 |
--------------------------------------------------------------------------------
/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/mbobiosio/ModularDynamicFeatureHilt/5a9caa07258a06596deaa07b32edce32c928eaf3/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Mon Jan 10 11:35:41 ICT 2022
2 | distributionBase=GRADLE_USER_HOME
3 | distributionUrl=https\://services.gradle.org/distributions/gradle-7.4.2-bin.zip
4 | distributionPath=wrapper/dists
5 | zipStorePath=wrapper/dists
6 | zipStoreBase=GRADLE_USER_HOME
7 |
--------------------------------------------------------------------------------
/gradlew:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env sh
2 |
3 | #
4 | # Copyright 2015 the original author or authors.
5 | #
6 | # Licensed under the Apache License, Version 2.0 (the "License");
7 | # you may not use this file except in compliance with the License.
8 | # You may obtain a copy of the License at
9 | #
10 | # https://www.apache.org/licenses/LICENSE-2.0
11 | #
12 | # Unless required by applicable law or agreed to in writing, software
13 | # distributed under the License is distributed on an "AS IS" BASIS,
14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 | # See the License for the specific language governing permissions and
16 | # limitations under the License.
17 | #
18 |
19 | ##############################################################################
20 | ##
21 | ## Gradle start up script for UN*X
22 | ##
23 | ##############################################################################
24 |
25 | # Attempt to set APP_HOME
26 | # Resolve links: $0 may be a link
27 | PRG="$0"
28 | # Need this for relative symlinks.
29 | while [ -h "$PRG" ] ; do
30 | ls=`ls -ld "$PRG"`
31 | link=`expr "$ls" : '.*-> \(.*\)$'`
32 | if expr "$link" : '/.*' > /dev/null; then
33 | PRG="$link"
34 | else
35 | PRG=`dirname "$PRG"`"/$link"
36 | fi
37 | done
38 | SAVED="`pwd`"
39 | cd "`dirname \"$PRG\"`/" >/dev/null
40 | APP_HOME="`pwd -P`"
41 | cd "$SAVED" >/dev/null
42 |
43 | APP_NAME="Gradle"
44 | APP_BASE_NAME=`basename "$0"`
45 |
46 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
47 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
48 |
49 | # Use the maximum available, or set MAX_FD != -1 to use that value.
50 | MAX_FD="maximum"
51 |
52 | warn () {
53 | echo "$*"
54 | }
55 |
56 | die () {
57 | echo
58 | echo "$*"
59 | echo
60 | exit 1
61 | }
62 |
63 | # OS specific support (must be 'true' or 'false').
64 | cygwin=false
65 | msys=false
66 | darwin=false
67 | nonstop=false
68 | case "`uname`" in
69 | CYGWIN* )
70 | cygwin=true
71 | ;;
72 | Darwin* )
73 | darwin=true
74 | ;;
75 | MINGW* )
76 | msys=true
77 | ;;
78 | NONSTOP* )
79 | nonstop=true
80 | ;;
81 | esac
82 |
83 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
84 |
85 |
86 | # Determine the Java command to use to start the JVM.
87 | if [ -n "$JAVA_HOME" ] ; then
88 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
89 | # IBM's JDK on AIX uses strange locations for the executables
90 | JAVACMD="$JAVA_HOME/jre/sh/java"
91 | else
92 | JAVACMD="$JAVA_HOME/bin/java"
93 | fi
94 | if [ ! -x "$JAVACMD" ] ; then
95 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
96 |
97 | Please set the JAVA_HOME variable in your environment to match the
98 | location of your Java installation."
99 | fi
100 | else
101 | JAVACMD="java"
102 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
103 |
104 | Please set the JAVA_HOME variable in your environment to match the
105 | location of your Java installation."
106 | fi
107 |
108 | # Increase the maximum file descriptors if we can.
109 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
110 | MAX_FD_LIMIT=`ulimit -H -n`
111 | if [ $? -eq 0 ] ; then
112 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
113 | MAX_FD="$MAX_FD_LIMIT"
114 | fi
115 | ulimit -n $MAX_FD
116 | if [ $? -ne 0 ] ; then
117 | warn "Could not set maximum file descriptor limit: $MAX_FD"
118 | fi
119 | else
120 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
121 | fi
122 | fi
123 |
124 | # For Darwin, add options to specify how the application appears in the dock
125 | if $darwin; then
126 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
127 | fi
128 |
129 | # For Cygwin or MSYS, switch paths to Windows format before running java
130 | if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
131 | APP_HOME=`cygpath --path --mixed "$APP_HOME"`
132 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
133 |
134 | JAVACMD=`cygpath --unix "$JAVACMD"`
135 |
136 | # We build the pattern for arguments to be converted via cygpath
137 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
138 | SEP=""
139 | for dir in $ROOTDIRSRAW ; do
140 | ROOTDIRS="$ROOTDIRS$SEP$dir"
141 | SEP="|"
142 | done
143 | OURCYGPATTERN="(^($ROOTDIRS))"
144 | # Add a user-defined pattern to the cygpath arguments
145 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then
146 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
147 | fi
148 | # Now convert the arguments - kludge to limit ourselves to /bin/sh
149 | i=0
150 | for arg in "$@" ; do
151 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
152 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
153 |
154 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
155 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
156 | else
157 | eval `echo args$i`="\"$arg\""
158 | fi
159 | i=`expr $i + 1`
160 | done
161 | case $i in
162 | 0) set -- ;;
163 | 1) set -- "$args0" ;;
164 | 2) set -- "$args0" "$args1" ;;
165 | 3) set -- "$args0" "$args1" "$args2" ;;
166 | 4) set -- "$args0" "$args1" "$args2" "$args3" ;;
167 | 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
168 | 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
169 | 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
170 | 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
171 | 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
172 | esac
173 | fi
174 |
175 | # Escape application args
176 | save () {
177 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
178 | echo " "
179 | }
180 | APP_ARGS=`save "$@"`
181 |
182 | # Collect all arguments for the java command, following the shell quoting and substitution rules
183 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
184 |
185 | exec "$JAVACMD" "$@"
186 |
--------------------------------------------------------------------------------
/gradlew.bat:
--------------------------------------------------------------------------------
1 | @rem
2 | @rem Copyright 2015 the original author or authors.
3 | @rem
4 | @rem Licensed under the Apache License, Version 2.0 (the "License");
5 | @rem you may not use this file except in compliance with the License.
6 | @rem You may obtain a copy of the License at
7 | @rem
8 | @rem https://www.apache.org/licenses/LICENSE-2.0
9 | @rem
10 | @rem Unless required by applicable law or agreed to in writing, software
11 | @rem distributed under the License is distributed on an "AS IS" BASIS,
12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | @rem See the License for the specific language governing permissions and
14 | @rem limitations under the License.
15 | @rem
16 |
17 | @if "%DEBUG%" == "" @echo off
18 | @rem ##########################################################################
19 | @rem
20 | @rem Gradle startup script for Windows
21 | @rem
22 | @rem ##########################################################################
23 |
24 | @rem Set local scope for the variables with windows NT shell
25 | if "%OS%"=="Windows_NT" setlocal
26 |
27 | set DIRNAME=%~dp0
28 | if "%DIRNAME%" == "" set DIRNAME=.
29 | set APP_BASE_NAME=%~n0
30 | set APP_HOME=%DIRNAME%
31 |
32 | @rem Resolve any "." and ".." in APP_HOME to make it shorter.
33 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
34 |
35 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
36 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
37 |
38 | @rem Find java.exe
39 | if defined JAVA_HOME goto findJavaFromJavaHome
40 |
41 | set JAVA_EXE=java.exe
42 | %JAVA_EXE% -version >NUL 2>&1
43 | if "%ERRORLEVEL%" == "0" goto execute
44 |
45 | echo.
46 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
47 | echo.
48 | echo Please set the JAVA_HOME variable in your environment to match the
49 | echo location of your Java installation.
50 |
51 | goto fail
52 |
53 | :findJavaFromJavaHome
54 | set JAVA_HOME=%JAVA_HOME:"=%
55 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
56 |
57 | if exist "%JAVA_EXE%" goto execute
58 |
59 | echo.
60 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
61 | echo.
62 | echo Please set the JAVA_HOME variable in your environment to match the
63 | echo location of your Java installation.
64 |
65 | goto fail
66 |
67 | :execute
68 | @rem Setup the command line
69 |
70 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
71 |
72 |
73 | @rem Execute Gradle
74 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
75 |
76 | :end
77 | @rem End local scope for the variables with windows NT shell
78 | if "%ERRORLEVEL%"=="0" goto mainEnd
79 |
80 | :fail
81 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
82 | rem the _cmd.exe /c_ return code!
83 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
84 | exit /b 1
85 |
86 | :mainEnd
87 | if "%OS%"=="Windows_NT" endlocal
88 |
89 | :omega
90 |
--------------------------------------------------------------------------------
/settings.gradle.kts:
--------------------------------------------------------------------------------
1 | pluginManagement {
2 | resolutionStrategy {
3 | eachPlugin {
4 | when (requested.id.id) {
5 | "androidx.navigation" -> {
6 | useModule("androidx.navigation:navigation-safe-args-gradle-plugin:2.4.2")
7 | }
8 | "dagger.hilt.android.plugin" -> {
9 | useModule("com.google.dagger:hilt-android-gradle-plugin:2.41")
10 | }
11 | }
12 | }
13 | }
14 |
15 | repositories {
16 | gradlePluginPortal()
17 | google()
18 | mavenCentral()
19 | }
20 | }
21 |
22 | dependencyResolutionManagement {
23 | repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
24 | repositories {
25 | google()
26 | mavenCentral()
27 | }
28 | }
29 | rootProject.name = "ModularDynamicFeatureHilt"
30 | include(
31 | ":app",
32 | ":common",
33 | ":data",
34 | ":domain",
35 | ":features:home",
36 | ":features:favorite",
37 | ":features:account"
38 | )
39 |
--------------------------------------------------------------------------------