├── .github └── workflows │ ├── build.yml │ └── pullrequest.yml ├── .gitignore ├── .idea ├── .gitignore ├── .name ├── compiler.xml ├── git_toolbox_prj.xml ├── gradle.xml ├── jarRepositories.xml ├── misc.xml └── vcs.xml ├── LICENSE ├── README.md ├── app ├── .gitignore ├── build.gradle.kts ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── com │ │ └── mbobiosio │ │ └── modularapp │ │ └── ExampleInstrumentedTest.kt │ ├── main │ ├── AndroidManifest.xml │ ├── java │ │ └── com │ │ │ └── mbobiosio │ │ │ └── modularapp │ │ │ ├── DynamicApp.kt │ │ │ ├── MainActivity.kt │ │ │ ├── di │ │ │ ├── AppModule.kt │ │ │ ├── DynamicFeatureDependencies.kt │ │ │ ├── ViewModelFactory.kt │ │ │ ├── ViewModelKey.kt │ │ │ └── ViewModelModule.kt │ │ │ └── util │ │ │ └── FragmentExt.kt │ └── res │ │ ├── drawable-v24 │ │ └── ic_launcher_foreground.xml │ │ ├── drawable │ │ ├── ic_account_circle.xml │ │ ├── ic_favorite_border.xml │ │ ├── ic_home.xml │ │ └── ic_launcher_background.xml │ │ ├── layout │ │ └── activity_main.xml │ │ ├── menu │ │ └── bottom_menu.xml │ │ ├── mipmap-anydpi-v26 │ │ ├── ic_launcher.xml │ │ └── ic_launcher_round.xml │ │ ├── mipmap-hdpi │ │ ├── ic_launcher.webp │ │ └── ic_launcher_round.webp │ │ ├── mipmap-mdpi │ │ ├── ic_launcher.webp │ │ └── ic_launcher_round.webp │ │ ├── mipmap-xhdpi │ │ ├── ic_launcher.webp │ │ └── ic_launcher_round.webp │ │ ├── mipmap-xxhdpi │ │ ├── ic_launcher.webp │ │ └── ic_launcher_round.webp │ │ ├── mipmap-xxxhdpi │ │ ├── ic_launcher.webp │ │ └── ic_launcher_round.webp │ │ ├── navigation │ │ └── nav_graph.xml │ │ └── values │ │ ├── ids.xml │ │ └── strings.xml │ └── test │ └── java │ └── com │ └── mbobiosio │ └── modularapp │ └── ExampleUnitTest.kt ├── assets ├── account.png ├── clean_arch.png ├── favorite.png └── home.png ├── build.gradle.kts ├── buildSrc ├── .gitignore ├── build.gradle.kts └── src │ └── main │ └── kotlin │ ├── Dependencies.kt │ ├── Modules.kt │ ├── Plugins.kt │ ├── Versions.kt │ └── extensions │ ├── DepHandler.kt │ └── Extensions.kt ├── common ├── .gitignore ├── build.gradle.kts ├── consumer-rules.pro ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── com │ │ └── mbobiosio │ │ └── common │ │ └── ExampleInstrumentedTest.kt │ ├── main │ ├── AndroidManifest.xml │ ├── java │ │ └── com │ │ │ └── mbobiosio │ │ │ └── common │ │ │ ├── base │ │ │ ├── BaseBindingActivity.kt │ │ │ └── BaseBindingFragment.kt │ │ │ └── util │ │ │ └── NavManager.kt │ └── res │ │ ├── values-night │ │ └── themes.xml │ │ └── values │ │ ├── colors.xml │ │ └── themes.xml │ └── test │ └── java │ └── com │ └── mbobiosio │ └── common │ └── ExampleUnitTest.kt ├── config └── detekt │ └── detekt.yml ├── data ├── .gitignore ├── build.gradle.kts ├── consumer-rules.pro ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── com │ │ └── mbobiosio │ │ └── data │ │ └── ExampleInstrumentedTest.kt │ ├── main │ ├── AndroidManifest.xml │ └── java │ │ └── com │ │ └── mbobiosio │ │ └── data │ │ └── SampleRepositoryImpl.kt │ └── test │ └── java │ └── com │ └── mbobiosio │ └── data │ └── ExampleUnitTest.kt ├── domain ├── .gitignore ├── build.gradle.kts ├── consumer-rules.pro ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── com │ │ └── mbobiosio │ │ └── domain │ │ └── ExampleInstrumentedTest.kt │ ├── main │ ├── AndroidManifest.xml │ └── java │ │ └── com │ │ └── mbobiosio │ │ └── domain │ │ └── SampleRepository.kt │ └── test │ └── java │ └── com │ └── mbobiosio │ └── domain │ └── ExampleUnitTest.kt ├── features ├── account │ ├── .gitignore │ ├── build.gradle.kts │ └── src │ │ ├── androidTest │ │ └── java │ │ │ └── com │ │ │ └── mbobiosio │ │ │ └── account │ │ │ └── ExampleInstrumentedTest.kt │ │ ├── main │ │ ├── AndroidManifest.xml │ │ ├── java │ │ │ └── com │ │ │ │ └── mbobiosio │ │ │ │ └── account │ │ │ │ ├── di │ │ │ │ ├── DynamicFeatureComponent.kt │ │ │ │ └── DynamicFeatureModule.kt │ │ │ │ ├── presentation │ │ │ │ └── AccountFragment.kt │ │ │ │ └── viewmodel │ │ │ │ └── AccountViewModel.kt │ │ └── res │ │ │ ├── drawable │ │ │ └── ic_account_circle.xml │ │ │ ├── layout │ │ │ └── fragment_account.xml │ │ │ └── values │ │ │ └── strings.xml │ │ └── test │ │ └── java │ │ └── com │ │ └── mbobiosio │ │ └── account │ │ └── ExampleUnitTest.kt ├── favorite │ ├── .gitignore │ ├── build.gradle.kts │ └── src │ │ ├── androidTest │ │ └── java │ │ │ └── com │ │ │ └── mbobiosio │ │ │ └── favorite │ │ │ └── ExampleInstrumentedTest.kt │ │ ├── main │ │ ├── AndroidManifest.xml │ │ ├── java │ │ │ └── com │ │ │ │ └── mbobiosio │ │ │ │ └── favorite │ │ │ │ ├── di │ │ │ │ ├── DynamicFeatureComponent.kt │ │ │ │ └── DynamicFeatureModule.kt │ │ │ │ ├── presentation │ │ │ │ └── FavoriteFragment.kt │ │ │ │ └── viewmodel │ │ │ │ └── FavoriteViewModel.kt │ │ └── res │ │ │ ├── drawable │ │ │ └── ic_favorite_border.xml │ │ │ ├── layout │ │ │ └── favorite_fragment.xml │ │ │ └── values │ │ │ └── strings.xml │ │ └── test │ │ └── java │ │ └── com │ │ └── mbobiosio │ │ └── favorite │ │ └── ExampleUnitTest.kt └── home │ ├── .gitignore │ ├── build.gradle.kts │ └── src │ ├── androidTest │ └── java │ │ └── com │ │ └── mbobiosio │ │ └── home │ │ └── ExampleInstrumentedTest.kt │ ├── main │ ├── AndroidManifest.xml │ ├── java │ │ └── com │ │ │ └── mbobiosio │ │ │ └── home │ │ │ ├── di │ │ │ ├── DynamicFeatureComponent.kt │ │ │ └── DynamicFeatureModule.kt │ │ │ ├── presentation │ │ │ └── HomeFragment.kt │ │ │ └── viewmodel │ │ │ └── HomeViewModel.kt │ └── res │ │ ├── drawable │ │ └── ic_add_home.xml │ │ ├── layout │ │ └── fragment_home.xml │ │ └── values │ │ └── strings.xml │ └── test │ └── java │ └── com │ └── mbobiosio │ └── home │ └── ExampleUnitTest.kt ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat └── settings.gradle.kts /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: Build 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | 8 | jobs: 9 | test: 10 | name: Run Unit Tests 11 | runs-on: ubuntu-latest 12 | steps: 13 | - uses: actions/checkout@v1 14 | - name: set up JDK 11 15 | uses: actions/setup-java@v1 16 | with: 17 | java-version: 11 18 | - name: Unit tests 19 | run: bash ./gradlew test --stacktrace 20 | build_apk: 21 | name: Generate APK 22 | runs-on: ubuntu-latest 23 | steps: 24 | - name: Checkout 25 | uses: actions/checkout@v1 26 | - name: Setup JDK 27 | uses: actions/setup-java@v1 28 | with: 29 | java-version: 11 30 | - name: Build APK 31 | run: bash ./gradlew assembleRelease --stacktrace 32 | - name: Upload APK 33 | uses: actions/upload-artifact@v2 34 | with: 35 | name: build_release_apk 36 | path: app/build/outputs/apk/release/app-release.apk 37 | retention-days: 2 -------------------------------------------------------------------------------- /.github/workflows/pullrequest.yml: -------------------------------------------------------------------------------- 1 | name: Build # name of the workflow 2 | 3 | on: 4 | pull_request: # specifies events to trigger the workflow 5 | branches: [ main, develop ] # branches that trigger the workflow 6 | 7 | jobs: # groups the jobs to be executed in this workflow 8 | 9 | build: # defines a job called build 10 | name: 🔨 Build # [optional] name of the job 11 | runs-on: ubuntu-latest # the job will be executed on ubuntu runner. Other include: Microsoft Windows & MacOS runners 12 | steps: # groups together all the steps that run in build job 13 | 14 | - name: Checkout code # [optional] specifies the name of the step 15 | uses: actions/checkout@v2 # specifies which version of action to run 16 | 17 | - name: Set up JDK 11 18 | uses: actions/setup-java@v1 19 | with: 20 | java-version: 11 21 | 22 | - name: Make gradlew executable 23 | run: chmod +x ./gradlew 24 | 25 | - name: Build with gradle 26 | run: ./gradlew build 27 | 28 | - name: Cache Gradle and wrapper 29 | uses: actions/cache@v2 30 | with: 31 | path: | 32 | ~/.gradle/caches 33 | ~/.gradle/wrapper 34 | key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*') }} 35 | restore-keys: | 36 | ${{ runner.os }}-gradle- 37 | - name: Build with Gradle 38 | run: bash ./gradlew build -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | /local.properties 4 | /.idea/caches 5 | /.idea/libraries 6 | /.idea/modules.xml 7 | /.idea/workspace.xml 8 | /.idea/navEditor.xml 9 | /.idea/assetWizardSettings.xml 10 | .DS_Store 11 | /build 12 | /captures 13 | .externalNativeBuild 14 | .cxx 15 | local.properties 16 | -------------------------------------------------------------------------------- /.idea/.gitignore: -------------------------------------------------------------------------------- 1 | # Default ignored files 2 | /shelf/ 3 | /workspace.xml 4 | -------------------------------------------------------------------------------- /.idea/.name: -------------------------------------------------------------------------------- 1 | ModularDynamicFeatureHilt -------------------------------------------------------------------------------- /.idea/compiler.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /.idea/git_toolbox_prj.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 14 | 15 | -------------------------------------------------------------------------------- /.idea/gradle.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 27 | 28 | -------------------------------------------------------------------------------- /.idea/jarRepositories.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 9 | 10 | 14 | 15 | 19 | 20 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 22 | 23 | 24 | 25 | 26 | 27 | 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 | [![Build](https://github.com/mbobiosio/ModularDynamicFeatureHilt/workflows/Build/badge.svg?branch=main)](https://github.com/mbobiosio/ModularDynamicFeatureHilt/actions?query=workflow%3ABuild) 8 | 9 | [![GitHub license](https://img.shields.io/badge/License-MIT-blue.svg)](LICENSE) 10 | [![ktlint](https://img.shields.io/badge/code%20style-%E2%9D%A4-FF4081.svg)](https://ktlint.github.io/) 11 | ![Twitter Follow](https://img.shields.io/twitter/follow/cazewonder?label=Follow&style=social) 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 |

Clean Architecture Diagram

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 | Home Favorite Account 109 | 110 | ### Author 111 | 112 | * [Mbuodile Obiosio](https://www.linkedin.com/in/mb-obiosio/) 113 | * [![Follow me](https://img.shields.io/twitter/follow/cazewonder?style=social)](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 | 3 | 4 | 8 | 9 | 13 | 14 | 18 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher.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 | --------------------------------------------------------------------------------