├── .editorconfig
├── .github
├── pull_request_template.md
└── workflows
│ ├── android_build.yml
│ ├── android_ui_tests.yml
│ └── template_change_test.yml
├── .gitignore
├── Dangerfile
├── Gemfile
├── README.md
├── app
├── .gitignore
├── build.gradle.kts
├── proguard-rules.pro
└── src
│ └── main
│ ├── AndroidManifest.xml
│ ├── java
│ └── template
│ │ ├── MainActivity.kt
│ │ └── theme
│ │ ├── Color.kt
│ │ ├── Shape.kt
│ │ ├── Theme.kt
│ │ └── Type.kt
│ └── res
│ ├── drawable-v24
│ └── ic_launcher_foreground.xml
│ ├── drawable
│ └── ic_launcher_background.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
│ ├── values-night
│ └── themes.xml
│ └── values
│ ├── colors.xml
│ ├── strings.xml
│ └── themes.xml
├── build-logic
├── README.md
├── convention
│ ├── build.gradle.kts
│ └── src
│ │ └── main
│ │ └── kotlin
│ │ ├── AndroidApplicationComposeConventionPlugin.kt
│ │ ├── AndroidApplicationConventionPlugin.kt
│ │ ├── AndroidApplicationJacocoConventionPlugin.kt
│ │ ├── AndroidFeatureConventionPlugin.kt
│ │ ├── AndroidHiltConventionPlugin.kt
│ │ ├── AndroidLibraryComposeConventionPlugin.kt
│ │ ├── AndroidLibraryConventionPlugin.kt
│ │ ├── AndroidLibraryJacocoConventionPlugin.kt
│ │ ├── AndroidRoomConventionPlugin.kt
│ │ ├── AndroidTestConventionPlugin.kt
│ │ ├── FirebasePerfConventionPlugin.kt
│ │ ├── KotlinLinterPlugin.kt
│ │ └── template
│ │ ├── AndroidCompose.kt
│ │ ├── Flavor.kt
│ │ ├── Jacoco.kt
│ │ ├── KotlinAndroid.kt
│ │ ├── PrintTestApks.kt
│ │ └── ProjectExtensions.kt
├── gradle.properties
├── gradle
│ └── wrapper
│ │ ├── gradle-wrapper.jar
│ │ └── gradle-wrapper.properties
└── settings.gradle.kts
├── build.gradle.kts
├── buildscripts
├── githooks.gradle
└── setup.gradle
├── config
└── detekt
│ └── detekt.yml
├── documentation
├── GitHooks.md
├── GitHubActions.md
└── StaticAnalysis.md
├── git-hooks
├── pre-commit.sh
└── pre-push.sh
├── gradle.properties
├── gradle
├── libs.versions.toml
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
└── settings.gradle.kts
/.editorconfig:
--------------------------------------------------------------------------------
1 | root = true
2 |
3 | [*.{kt,kts}]
4 | max_line_length = 140
5 | indent_size = 4
6 | insert_final_newline = true
7 | ij_kotlin_allow_trailing_comma = true
8 | ij_kotlin_allow_trailing_comma_on_call_site = true
9 | ktlint_disabled_rules = filename
10 |
--------------------------------------------------------------------------------
/.github/pull_request_template.md:
--------------------------------------------------------------------------------
1 | ## Summary
2 |
3 |
4 |
5 | ## How It Was Tested
6 |
7 |
8 |
9 | ## Screenshot/Gif
10 |
11 |
12 |
13 |
14 |
15 | Screenshot Name
16 |
17 |
18 |
19 |
--------------------------------------------------------------------------------
/.github/workflows/android_build.yml:
--------------------------------------------------------------------------------
1 | name: Android Build
2 |
3 | on:
4 | push:
5 | branches:
6 | - develop
7 | - main
8 | pull_request:
9 |
10 | jobs:
11 | build:
12 | runs-on: ubuntu-latest
13 | steps:
14 | - uses: actions/checkout@v3
15 |
16 | - name: Set Up JDK
17 | uses: actions/setup-java@v3
18 | with:
19 | distribution: 'zulu'
20 | java-version: 17
21 |
22 | - name: Setup Gradle
23 | uses: gradle/gradle-build-action@v2
24 | with:
25 | # Only write to the cache for builds on the 'development' branch
26 | cache-read-only: ${{ github.ref != 'refs/heads/development' }}
27 |
28 | - name: Build Project
29 | run: ./gradlew assemble
30 |
31 | - name: Run Tests
32 | run: ./gradlew test
33 |
34 | - name: Lint Checks
35 | run: ./gradlew detektAll lintKotlin lint
--------------------------------------------------------------------------------
/.github/workflows/android_ui_tests.yml:
--------------------------------------------------------------------------------
1 | name: Android UI Tests
2 |
3 | on:
4 | push:
5 | branches:
6 | - develop
7 | pull_request:
8 |
9 | jobs:
10 | android-test:
11 | runs-on: macos-latest
12 | steps:
13 | - name: Checkout
14 | uses: actions/checkout@v3
15 |
16 | - name: Set Up JDK
17 | uses: actions/setup-java@v3
18 | with:
19 | distribution: 'zulu'
20 | java-version: 17
21 |
22 | - name: Setup Gradle
23 | uses: gradle/gradle-build-action@v2
24 | with:
25 | # Only write to the cache for builds on the 'development' branch
26 | cache-read-only: ${{ github.ref != 'refs/heads/development' }}
27 |
28 | - name: Run Tests
29 | uses: reactivecircus/android-emulator-runner@v2
30 | with:
31 | api-level: 29
32 | script: ./gradlew app:connectedCheck
--------------------------------------------------------------------------------
/.github/workflows/template_change_test.yml:
--------------------------------------------------------------------------------
1 | name: Rename Template
2 |
3 | on: pull_request
4 |
5 | jobs:
6 | rename-template:
7 | strategy:
8 | matrix:
9 | useHiltDependencies: [true, false]
10 | useRoomDependencies: [true, false]
11 | useRetrofitDependencies: [true, false]
12 |
13 | runs-on: ubuntu-latest
14 | steps:
15 | - uses: actions/checkout@v3
16 |
17 | - name: Set useHiltDependencies
18 | uses: jacobtomlinson/gha-find-replace@v3
19 | with:
20 | find: "useHiltDependencies[ ]+ : true"
21 | replace: "useHiltDependencies : ${{ matrix.useHiltDependencies }}"
22 | include: "**setup.gradle"
23 |
24 | - name: Set useRoomDependencies
25 | uses: jacobtomlinson/gha-find-replace@v3
26 | with:
27 | find: "useRoomDependencies[ ]+ : true"
28 | replace: "useRoomDependencies : ${{ matrix.useRoomDependencies }}"
29 | include: "**setup.gradle"
30 |
31 | - name: Set useRetrofitDependencies
32 | uses: jacobtomlinson/gha-find-replace@v3
33 | with:
34 | find: "useRetrofitDependencies[ ]+ : true"
35 | replace: "useRetrofitDependencies : ${{ matrix.useRetrofitDependencies }}"
36 | include: "**setup.gradle"
37 |
38 | - name: Set Up JDK
39 | uses: actions/setup-java@v3
40 | with:
41 | distribution: 'zulu'
42 | java-version: 17
43 |
44 | - name: Setup Gradle
45 | uses: gradle/gradle-build-action@v2
46 |
47 | - name: Rename
48 | run: ./gradlew renameTemplate
49 |
50 | - name: Build
51 | run: ./gradlew build
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | *.iml
2 | .gradle
3 | /local.properties
4 | /.idea/
5 | .DS_Store
6 | /build
7 | /captures
8 | .externalNativeBuild
9 | .cxx
10 | local.properties
11 | build/
12 |
--------------------------------------------------------------------------------
/Dangerfile:
--------------------------------------------------------------------------------
1 | message "Thanks @#{github.pr_author}!"
2 |
3 | if github.pr_body.length == 0
4 | fail "Please provide a summary in the Pull Request description."
5 | end
6 |
7 | if git.lines_of_code > 500
8 | warn "Please consider breaking up this pull request."
9 | end
10 |
11 | if github.pr_labels.empty?
12 | warn "Please add labels to this PR."
13 | end
14 |
15 | if git.deletions > git.insertions
16 | message "🎉 Code Cleanup!"
17 | end
18 |
19 | # Notify of outdated dependencies
20 | dependencyUpdatesHeader = "The following dependencies have later milestone versions:"
21 | dependencyReportsFile = "build/dependencyUpdates/report.txt"
22 |
23 | # Due to the formatting of this output file, we first check if there are any dependencies
24 | # by looking for a `->` arrow, then we check for the relevant headers. We do this to handle a case
25 | # where there are no app dependencies but there are Gradle dependencies.
26 | hasDependencyUpdatesHeader = File.readlines(dependencyReportsFile).grep(/#{dependencyUpdatesHeader}/).any?
27 |
28 | if hasDependencyUpdatesHeader
29 | file = File.open(dependencyReportsFile, "rb").read
30 | index = file.index(dependencyUpdatesHeader)
31 | message file.slice(index..-1)
32 | end
--------------------------------------------------------------------------------
/Gemfile:
--------------------------------------------------------------------------------
1 | source "https://rubygems.org"
2 |
3 | git_source(:github) {|repo_name| "https://github.com/#{repo_name}" }
4 |
5 | gem "danger"
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Android App Template
2 |
3 | This GitHub template repository serves as a starting point for developing Android applications.
4 | I've started this Android application template using [Adam](https://github.com/AdamMc331)'s template.
5 | Along with the existing tools, I've added some features that align with my specific needs. These enhancements aim to make the development process smoother and improve the app's functionality. However, I'm open to suggestions and improvements. If you think there are other important elements to include or have recommendations for modifying the current setup, please share your thoughts. Your input is highly appreciated.
6 | Note that, since Adam did a really good job with the documentation, I left it mostly unchanged.
7 |
8 | ## Why This Template?
9 |
10 | The purpose of this template is to avoid any opinions on writing code. The developers should have the freedom to choose their own architecture, third party dependencies, package structure, and more.
11 |
12 | This template _is_ opinionated about developer tooling. Dependency management is configured, git hooks are defined, code formatting and static analysis are all there, and it even has pull request templates. The purpose of this repo is to help you get started building your next project with confidence in your code, and not telling you how to write it.
13 |
14 | ## Walkthrough
15 |
16 | If you'd like a video walk through of this template and all it has to offer, you can find that on YouTube.
17 |
18 | https://youtu.be/E0iMUWJn76E
19 |
20 | ## Using This Template
21 |
22 | To use this template in your own project, click the "Use this template" button at the top right of the repository. Once you do, a repository will be created for your account that you can clone and use on your device.
23 |
24 | To setup this repository to your needs, open the [setup.gradle](buildscripts/setup.gradle) file
25 | and tweak the `renameConfig` block to your needs. After that, you can run the `renameTemplate`
26 | gradle task to have the app module's package name and relevant strings replaced.
27 |
28 | ## What's Included
29 |
30 | A number of third party dependencies are included in this template. They are also documented inside the [documentation folder](/documentation). The files inside this documentation folder are written in such a way that you can keep them in your real project, to let team members read up on why dependencies are included and how they work.
31 |
32 | The dependencies in the template include:
33 |
34 | * [Ktlint](/documentation/StaticAnalysis.md) for formatting.
35 | * [Detekt](/documentation/StaticAnalysis.md) for code smells.
36 | * [Git Hooks](/documentation/GitHooks.md) for automatically perform static analysis checks.
37 | * [GitHub Actions](/documentation/GitHubActions.md) for running continuous integration and ensuring code quality with every PR.
38 | * [LeakCanary](https://square.github.io/leakcanary/) for detecting memory leaks.
39 | * [Hilt](https://developer.android.com/training/dependency-injection/hilt-android) dependencies, which can be removed via setup.gradle if necessary.
40 | * [Room](https://developer.android.com/training/data-storage/room) dependencies, which can be removed via setup.gradle if necessary.
41 |
42 | ### Templates
43 |
44 | There are also templates within this template. This repo comes shipped with a [Pull Request Template](/.github/pull_request_template.md) that will help you and your team write organized and detailed pull request descriptions.
45 |
46 | ## Dependency Setup
47 |
48 | You may notice that dependencies are set up in a very specific way. Each of the tools has its own Gradle file in the [buildscripts folder](/buildscripts). This is by design so that if you chose to have a multi module project, these dependencies can easily be shared between them. This is already configured inside our root `build.gradle.kts` file, by applying to each sub project:
49 |
50 | ```groovy
51 | subprojects {
52 | apply from: "../buildscripts/versionsplugin.gradle"
53 | }
54 | ```
55 |
56 | In addition, all of the app module dependencies are defined using a gradle version catalog, found in this [toml](gradle/libs.versions.toml) file.
57 |
58 |
--------------------------------------------------------------------------------
/app/.gitignore:
--------------------------------------------------------------------------------
1 | /build
--------------------------------------------------------------------------------
/app/build.gradle.kts:
--------------------------------------------------------------------------------
1 | plugins {
2 | alias(libs.plugins.convention.android.application)
3 | alias(libs.plugins.convention.android.application.compose)
4 | alias(libs.plugins.convention.android.room)
5 | alias(libs.plugins.convention.android.hilt)
6 | alias(libs.plugins.convention.kotlinter)
7 | }
8 |
9 | android {
10 |
11 | defaultConfig {
12 | applicationId = "template.app.id"
13 | versionCode = 1
14 | versionName = "1.0"
15 |
16 | testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
17 | vectorDrawables {
18 | useSupportLibrary = true
19 | }
20 | }
21 |
22 | buildTypes {
23 | release {
24 | isMinifyEnabled = false
25 | proguardFiles(getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro")
26 | }
27 | }
28 |
29 | packaging {
30 | resources {
31 | excludes += "/META-INF/{AL2.0,LGPL2.1}"
32 | }
33 | }
34 |
35 | namespace = "template"
36 | }
37 |
38 | dependencies {
39 |
40 | implementation(libs.androidx.activity.compose)
41 | implementation(libs.androidx.appcompat)
42 | implementation(libs.androidx.core.ktx)
43 | implementation(libs.androidx.lifecycle.runtime.ktx)
44 | implementation(libs.android.material)
45 | implementation(libs.compose.ui)
46 | implementation(libs.compose.material)
47 | implementation(libs.compose.ui.tooling)
48 | implementation(libs.moshi.kotlin.core)
49 | implementation(libs.retrofit)
50 | implementation(libs.retrofit.converter.moshi)
51 |
52 | testImplementation(libs.junit4)
53 |
54 | androidTestImplementation(libs.androidx.test.junit)
55 | androidTestImplementation(libs.androidx.test.espresso.core)
56 | androidTestImplementation(libs.compose.ui.test.junit)
57 | androidTestImplementation(libs.hilt.android.testing)
58 |
59 | debugImplementation(libs.compose.ui.test.manifest)
60 | debugImplementation(libs.compose.ui.tooling)
61 | debugImplementation(libs.leakcanary)
62 |
63 | ksp(libs.moshi.kotlin.codegen)
64 | }
65 |
--------------------------------------------------------------------------------
/app/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # You can control the set of applied configuration files using the
3 | # proguardFiles setting in build.gradle.kts.
4 | #
5 | # For more details, see
6 | # http://developer.android.com/guide/developing/tools/proguard.html
7 |
8 | # If your project uses WebView with JS, uncomment the following
9 | # and specify the fully qualified class name to the JavaScript interface
10 | # class:
11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
12 | # public *;
13 | #}
14 |
15 | # Uncomment this to preserve the line number information for
16 | # debugging stack traces.
17 | #-keepattributes SourceFile,LineNumberTable
18 |
19 | # If you keep the line number information, uncomment this to
20 | # hide the original source file name.
21 | #-renamesourcefileattribute SourceFile
--------------------------------------------------------------------------------
/app/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
13 |
14 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
--------------------------------------------------------------------------------
/app/src/main/java/template/MainActivity.kt:
--------------------------------------------------------------------------------
1 | package template
2 |
3 | import android.os.Bundle
4 | import androidx.activity.ComponentActivity
5 | import androidx.activity.SystemBarStyle
6 | import androidx.activity.compose.setContent
7 | import androidx.activity.enableEdgeToEdge
8 | import androidx.compose.foundation.isSystemInDarkTheme
9 | import androidx.compose.foundation.layout.Column
10 | import androidx.compose.foundation.layout.WindowInsets
11 | import androidx.compose.foundation.layout.fillMaxSize
12 | import androidx.compose.foundation.layout.systemBars
13 | import androidx.compose.foundation.layout.windowInsetsPadding
14 | import androidx.compose.material3.Surface
15 | import androidx.compose.material3.Text
16 | import androidx.compose.runtime.Composable
17 | import androidx.compose.runtime.DisposableEffect
18 | import androidx.compose.ui.Modifier
19 | import template.theme.TemplateTheme
20 |
21 | class MainActivity : ComponentActivity() {
22 | override fun onCreate(savedInstanceState: Bundle?) {
23 | super.onCreate(savedInstanceState)
24 |
25 | enableEdgeToEdge()
26 |
27 | setContent {
28 | val darkTheme = isSystemInDarkTheme()
29 |
30 | SystemBarStyleEffect(darkTheme = darkTheme)
31 |
32 | TemplateTheme {
33 | Surface {
34 | Column(
35 | modifier = Modifier
36 | .fillMaxSize()
37 | .windowInsetsPadding(WindowInsets.systemBars),
38 | ) {
39 | Greeting("Android")
40 | }
41 | }
42 | }
43 | }
44 | }
45 |
46 | /**
47 | * Update the edge to edge configuration to match the theme
48 | * This is the same parameters as the default enableEdgeToEdge call, but we manually
49 | * resolve whether or not to show dark theme using uiState, since it can be different
50 | * than the configuration's dark theme value based on the user preference.
51 | */
52 | @Composable
53 | private fun SystemBarStyleEffect(darkTheme: Boolean) {
54 | DisposableEffect(darkTheme) {
55 | enableEdgeToEdge(
56 | statusBarStyle = SystemBarStyle.auto(
57 | android.graphics.Color.TRANSPARENT,
58 | android.graphics.Color.TRANSPARENT,
59 | ) { darkTheme },
60 | navigationBarStyle = SystemBarStyle.auto(
61 | lightScrim,
62 | darkScrim,
63 | ) { darkTheme },
64 | )
65 | onDispose {}
66 | }
67 | }
68 | }
69 |
70 | @Composable
71 | fun Greeting(name: String) {
72 | Text(text = "Hello $name!")
73 | }
74 |
75 | /**
76 | * The default light scrim, as defined by androidx and the platform:
77 | * https://cs.android.com/androidx/platform/frameworks/support/+/androidx-main:activity/activity/src/main/java/androidx/activity/EdgeToEdge.kt;l=35-38;drc=27e7d52e8604a080133e8b842db10c89b4482598
78 | */
79 | @Suppress("MagicNumber")
80 | private val lightScrim = android.graphics.Color.argb(0xe6, 0xFF, 0xFF, 0xFF)
81 |
82 | /**
83 | * The default dark scrim, as defined by androidx and the platform:
84 | * https://cs.android.com/androidx/platform/frameworks/support/+/androidx-main:activity/activity/src/main/java/androidx/activity/EdgeToEdge.kt;l=40-44;drc=27e7d52e8604a080133e8b842db10c89b4482598
85 | */
86 | @Suppress("MagicNumber")
87 | private val darkScrim = android.graphics.Color.argb(0x80, 0x1b, 0x1b, 0x1b)
88 |
--------------------------------------------------------------------------------
/app/src/main/java/template/theme/Color.kt:
--------------------------------------------------------------------------------
1 | @file:Suppress("MagicNumber")
2 |
3 | package template.theme
4 |
5 | import androidx.compose.ui.graphics.Color
6 |
7 | val Purple200 = Color(0xFFBB86FC)
8 | val Purple500 = Color(0xFF6200EE)
9 | val Teal200 = Color(0xFF03DAC5)
10 |
--------------------------------------------------------------------------------
/app/src/main/java/template/theme/Shape.kt:
--------------------------------------------------------------------------------
1 | package template.theme
2 |
3 | import androidx.compose.foundation.shape.RoundedCornerShape
4 | import androidx.compose.material3.Shapes
5 | import androidx.compose.ui.unit.dp
6 |
7 | val Shapes = Shapes(
8 | small = RoundedCornerShape(4.dp),
9 | medium = RoundedCornerShape(4.dp),
10 | large = RoundedCornerShape(0.dp),
11 | )
12 |
--------------------------------------------------------------------------------
/app/src/main/java/template/theme/Theme.kt:
--------------------------------------------------------------------------------
1 | package template.theme
2 |
3 | import android.annotation.TargetApi
4 | import android.os.Build
5 | import androidx.compose.foundation.isSystemInDarkTheme
6 | import androidx.compose.material3.MaterialTheme
7 | import androidx.compose.material3.darkColorScheme
8 | import androidx.compose.material3.dynamicDarkColorScheme
9 | import androidx.compose.material3.dynamicLightColorScheme
10 | import androidx.compose.material3.lightColorScheme
11 | import androidx.compose.runtime.Composable
12 | import androidx.compose.ui.platform.LocalContext
13 |
14 | private val darkColorScheme = darkColorScheme(
15 | primary = Purple200,
16 | secondary = Teal200,
17 | )
18 |
19 | private val lightColorScheme = lightColorScheme(
20 | primary = Purple500,
21 | secondary = Teal200,
22 | )
23 |
24 | @Composable
25 | @TargetApi(Build.VERSION_CODES.S)
26 | fun TemplateTheme(
27 | darkTheme: Boolean = isSystemInDarkTheme(),
28 | dynamicTheme: Boolean = Build.VERSION.SDK_INT >= Build.VERSION_CODES.S,
29 | content: @Composable () -> Unit,
30 | ) {
31 | val colorScheme = when {
32 | dynamicTheme && darkTheme -> dynamicDarkColorScheme(LocalContext.current)
33 | dynamicTheme && !darkTheme -> dynamicLightColorScheme(LocalContext.current)
34 | darkTheme -> darkColorScheme
35 | else -> lightColorScheme
36 | }
37 |
38 | MaterialTheme(
39 | colorScheme = colorScheme,
40 | typography = Typography,
41 | shapes = Shapes,
42 | content = content,
43 | )
44 | }
45 |
--------------------------------------------------------------------------------
/app/src/main/java/template/theme/Type.kt:
--------------------------------------------------------------------------------
1 | package template.theme
2 |
3 | import androidx.compose.material3.Typography
4 | import androidx.compose.ui.text.TextStyle
5 | import androidx.compose.ui.text.font.FontFamily
6 | import androidx.compose.ui.text.font.FontWeight
7 | import androidx.compose.ui.unit.sp
8 |
9 | // Set of Material typography styles to start with
10 | val Typography = Typography(
11 | bodyMedium = TextStyle(
12 | fontFamily = FontFamily.Default,
13 | fontWeight = FontWeight.Normal,
14 | fontSize = 16.sp,
15 | ),
16 | )
17 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable-v24/ic_launcher_foreground.xml:
--------------------------------------------------------------------------------
1 |
7 |
8 |
9 |
15 |
18 |
21 |
22 |
23 |
24 |
30 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_launcher_background.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
10 |
15 |
20 |
25 |
30 |
35 |
40 |
45 |
50 |
55 |
60 |
65 |
70 |
75 |
80 |
85 |
90 |
95 |
100 |
105 |
110 |
115 |
120 |
125 |
130 |
135 |
140 |
145 |
150 |
155 |
160 |
165 |
170 |
171 |
--------------------------------------------------------------------------------
/app/src/main/res/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/aslansari/android-app-template/5b914d05aed2854247669bc806d9c15adb0feff2/app/src/main/res/mipmap-hdpi/ic_launcher.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aslansari/android-app-template/5b914d05aed2854247669bc806d9c15adb0feff2/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aslansari/android-app-template/5b914d05aed2854247669bc806d9c15adb0feff2/app/src/main/res/mipmap-mdpi/ic_launcher.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aslansari/android-app-template/5b914d05aed2854247669bc806d9c15adb0feff2/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aslansari/android-app-template/5b914d05aed2854247669bc806d9c15adb0feff2/app/src/main/res/mipmap-xhdpi/ic_launcher.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aslansari/android-app-template/5b914d05aed2854247669bc806d9c15adb0feff2/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aslansari/android-app-template/5b914d05aed2854247669bc806d9c15adb0feff2/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aslansari/android-app-template/5b914d05aed2854247669bc806d9c15adb0feff2/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aslansari/android-app-template/5b914d05aed2854247669bc806d9c15adb0feff2/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aslansari/android-app-template/5b914d05aed2854247669bc806d9c15adb0feff2/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp
--------------------------------------------------------------------------------
/app/src/main/res/values-night/themes.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
16 |
--------------------------------------------------------------------------------
/app/src/main/res/values/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #FFBB86FC
4 | #FF6200EE
5 | #FF3700B3
6 | #FF03DAC5
7 | #FF018786
8 | #FF000000
9 | #FFFFFFFF
10 |
--------------------------------------------------------------------------------
/app/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/app/src/main/res/values/themes.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
16 |
17 |
21 |
22 |
23 |
24 |
25 |
--------------------------------------------------------------------------------
/build-logic/README.md:
--------------------------------------------------------------------------------
1 | # Convention Plugins
2 |
3 | The `build-logic` folder defines project-specific convention plugins, used to keep a single
4 | source of truth for common module configurations.
5 |
6 | This approach is heavily based on
7 | [https://developer.squareup.com/blog/herding-elephants/](https://developer.squareup.com/blog/herding-elephants/)
8 | and
9 | [https://github.com/jjohannes/idiomatic-gradle](https://github.com/jjohannes/idiomatic-gradle).
10 |
11 | By setting up convention plugins in `build-logic`, we can avoid duplicated build script setup,
12 | messy `subproject` configurations, without the pitfalls of the `buildSrc` directory.
13 |
14 | `build-logic` is an included build, as configured in the root
15 | [`settings.gradle.kts`](../settings.gradle.kts).
16 |
17 | Inside `build-logic` is a `convention` module, which defines a set of plugins that all normal
18 | modules can use to configure themselves.
19 |
20 | `build-logic` also includes a set of `Kotlin` files used to share logic between plugins themselves,
21 | which is most useful for configuring Android components (libraries vs applications) with shared
22 | code.
23 |
24 | These plugins are *additive* and *composable*, and try to only accomplish a single responsibility.
25 | Modules can then pick and choose the configurations they need.
26 | If there is one-off logic for a module without shared code, it's preferable to define that directly
27 | in the module's `build.gradle`, as opposed to creating a convention plugin with module-specific
28 | setup.
29 |
30 | Current list of convention plugins:
31 |
32 | - [`template.android.application`](convention/src/main/kotlin/AndroidApplicationConventionPlugin.kt),
33 | [`template.android.library`](convention/src/main/kotlin/AndroidLibraryConventionPlugin.kt),
34 | [`template.android.test`](convention/src/main/kotlin/AndroidTestConventionPlugin.kt):
35 | Configures common Android and Kotlin options.
36 | - [`template.android.application.compose`](convention/src/main/kotlin/AndroidApplicationComposeConventionPlugin.kt),
37 | [`template.android.library.compose`](convention/src/main/kotlin/AndroidLibraryComposeConventionPlugin.kt):
38 | Configures Jetpack Compose options
39 |
--------------------------------------------------------------------------------
/build-logic/convention/build.gradle.kts:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2022 The Android Open Source Project
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * https://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | plugins {
18 | `kotlin-dsl`
19 | }
20 |
21 | group = "template.buildlogic"
22 |
23 | java {
24 | sourceCompatibility = JavaVersion.VERSION_17
25 | targetCompatibility = JavaVersion.VERSION_17
26 | }
27 |
28 | dependencies {
29 | compileOnly(libs.android.gradlePlugin)
30 | compileOnly(libs.kotlin.gradlePlugin)
31 | compileOnly(libs.firebase.crashlytics.gradlePlugin)
32 | compileOnly(libs.ksp.gradlePlugin)
33 | }
34 |
35 | gradlePlugin {
36 | plugins {
37 | register("androidApplicationCompose") {
38 | id = "convention.android.application.compose"
39 | implementationClass = "AndroidApplicationComposeConventionPlugin"
40 | }
41 | register("androidApplication") {
42 | id = "convention.android.application"
43 | implementationClass = "AndroidApplicationConventionPlugin"
44 | }
45 | register("androidApplicationJacoco") {
46 | id = "convention.android.application.jacoco"
47 | implementationClass = "AndroidApplicationJacocoConventionPlugin"
48 | }
49 | register("androidLibraryCompose") {
50 | id = "convention.android.library.compose"
51 | implementationClass = "AndroidLibraryComposeConventionPlugin"
52 | }
53 | register("androidLibrary") {
54 | id = "convention.android.library"
55 | implementationClass = "AndroidLibraryConventionPlugin"
56 | }
57 | register("androidFeature") {
58 | id = "convention.android.feature"
59 | implementationClass = "AndroidFeatureConventionPlugin"
60 | }
61 | register("androidLibraryJacoco") {
62 | id = "convention.android.library.jacoco"
63 | implementationClass = "AndroidLibraryJacocoConventionPlugin"
64 | }
65 | register("androidTest") {
66 | id = "convention.android.test"
67 | implementationClass = "AndroidTestConventionPlugin"
68 | }
69 | register("androidRoom") {
70 | id = "convention.android.room"
71 | implementationClass = "AndroidRoomConventionPlugin"
72 | }
73 | register("androidHilt") {
74 | id = "convention.android.hilt"
75 | implementationClass = "AndroidHiltConventionPlugin"
76 | }
77 | register("firebase-perf") {
78 | id = "convention.firebase-perf"
79 | implementationClass = "FirebasePerfConventionPlugin"
80 | }
81 | register("kotlinter") {
82 | id = "convention.kotlinter"
83 | implementationClass = "KotlinLinterPlugin"
84 | }
85 | }
86 | }
87 |
--------------------------------------------------------------------------------
/build-logic/convention/src/main/kotlin/AndroidApplicationComposeConventionPlugin.kt:
--------------------------------------------------------------------------------
1 | import com.android.build.api.dsl.ApplicationExtension
2 | import template.configureAndroidCompose
3 | import org.gradle.api.Plugin
4 | import org.gradle.api.Project
5 | import org.gradle.kotlin.dsl.getByType
6 |
7 | class AndroidApplicationComposeConventionPlugin : Plugin {
8 | override fun apply(target: Project) {
9 | with(target) {
10 | pluginManager.apply("com.android.application")
11 | val extension = extensions.getByType()
12 | configureAndroidCompose(extension)
13 | }
14 | }
15 |
16 | }
17 |
--------------------------------------------------------------------------------
/build-logic/convention/src/main/kotlin/AndroidApplicationConventionPlugin.kt:
--------------------------------------------------------------------------------
1 | import com.android.build.api.variant.ApplicationAndroidComponentsExtension
2 | import com.android.build.api.dsl.ApplicationExtension
3 | import template.configureFlavors
4 | import template.configureKotlinAndroid
5 | import template.configurePrintApksTask
6 | import org.gradle.api.Plugin
7 | import org.gradle.api.Project
8 | import org.gradle.api.artifacts.VersionCatalogsExtension
9 | import org.gradle.kotlin.dsl.configure
10 | import org.gradle.kotlin.dsl.getByType
11 | import template.libs
12 |
13 | class AndroidApplicationConventionPlugin : Plugin {
14 | override fun apply(target: Project) {
15 | with(target) {
16 | with(pluginManager) {
17 | apply("com.android.application")
18 | apply("org.jetbrains.kotlin.android")
19 | }
20 |
21 | extensions.configure {
22 | configureKotlinAndroid(this)
23 | defaultConfig.targetSdk = libs.findVersion("compile-sdk").get().toString().toInt()
24 | configureFlavors(this)
25 | }
26 | extensions.configure {
27 | configurePrintApksTask(this)
28 | }
29 | }
30 | }
31 |
32 | }
33 |
--------------------------------------------------------------------------------
/build-logic/convention/src/main/kotlin/AndroidApplicationJacocoConventionPlugin.kt:
--------------------------------------------------------------------------------
1 | import com.android.build.api.variant.ApplicationAndroidComponentsExtension
2 | import template.configureJacoco
3 | import org.gradle.api.Plugin
4 | import org.gradle.api.Project
5 | import org.gradle.kotlin.dsl.getByType
6 |
7 | class AndroidApplicationJacocoConventionPlugin : Plugin {
8 | override fun apply(target: Project) {
9 | with(target) {
10 | with(pluginManager) {
11 | apply("org.gradle.jacoco")
12 | apply("com.android.application")
13 | }
14 | val extension = extensions.getByType()
15 | configureJacoco(extension)
16 | }
17 | }
18 |
19 | }
20 |
--------------------------------------------------------------------------------
/build-logic/convention/src/main/kotlin/AndroidFeatureConventionPlugin.kt:
--------------------------------------------------------------------------------
1 | import com.android.build.gradle.LibraryExtension
2 | import org.gradle.api.Plugin
3 | import org.gradle.api.Project
4 | import org.gradle.api.artifacts.VersionCatalogsExtension
5 | import org.gradle.kotlin.dsl.configure
6 | import org.gradle.kotlin.dsl.dependencies
7 | import org.gradle.kotlin.dsl.getByType
8 | import org.gradle.kotlin.dsl.kotlin
9 |
10 | class AndroidFeatureConventionPlugin : Plugin {
11 | override fun apply(target: Project) {
12 | with(target) {
13 | pluginManager.apply {
14 | apply("convention.android.library")
15 | apply("convention.android.hilt")
16 | }
17 | extensions.configure {
18 | defaultConfig {
19 | testInstrumentationRunner =
20 | "template.core.testing.AppTestRunner"
21 | }
22 | }
23 |
24 | val libs = extensions.getByType().named("libs")
25 |
26 | dependencies {
27 | add("implementation", project(":core:model"))
28 | add("implementation", project(":core:ui"))
29 | add("implementation", project(":core:designsystem"))
30 | add("implementation", project(":core:data"))
31 | add("implementation", project(":core:common"))
32 | add("implementation", project(":core:domain"))
33 |
34 | add("testImplementation", kotlin("test"))
35 | add("testImplementation", project(":core:testing"))
36 | add("androidTestImplementation", kotlin("test"))
37 | add("androidTestImplementation", project(":core:testing"))
38 |
39 | add("implementation", libs.findLibrary("coil.kt").get())
40 | add("implementation", libs.findLibrary("coil.kt.compose").get())
41 |
42 | add("implementation", libs.findLibrary("androidx.hilt.navigation.compose").get())
43 | add("implementation", libs.findLibrary("androidx.lifecycle.runtimeCompose").get())
44 | add("implementation", libs.findLibrary("androidx.lifecycle.viewModelCompose").get())
45 |
46 | add("implementation", libs.findLibrary("kotlinx.coroutines.android").get())
47 | }
48 | }
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/build-logic/convention/src/main/kotlin/AndroidHiltConventionPlugin.kt:
--------------------------------------------------------------------------------
1 | import org.gradle.api.Plugin
2 | import org.gradle.api.Project
3 | import org.gradle.api.artifacts.VersionCatalogsExtension
4 | import org.gradle.kotlin.dsl.dependencies
5 | import org.gradle.kotlin.dsl.getByType
6 |
7 | class AndroidHiltConventionPlugin : Plugin {
8 | override fun apply(target: Project) {
9 | with(target) {
10 | with(pluginManager) {
11 | apply("org.jetbrains.kotlin.kapt")
12 | apply("dagger.hilt.android.plugin")
13 | }
14 |
15 | val libs = extensions.getByType().named("libs")
16 | dependencies {
17 | "implementation"(libs.findLibrary("hilt.android.core").get())
18 | "kapt"(libs.findLibrary("hilt.compiler").get())
19 | "kaptAndroidTest"(libs.findLibrary("hilt.compiler").get())
20 | }
21 |
22 | }
23 | }
24 |
25 | }
26 |
--------------------------------------------------------------------------------
/build-logic/convention/src/main/kotlin/AndroidLibraryComposeConventionPlugin.kt:
--------------------------------------------------------------------------------
1 | import com.android.build.gradle.LibraryExtension
2 | import template.configureAndroidCompose
3 | import org.gradle.api.Plugin
4 | import org.gradle.api.Project
5 | import org.gradle.kotlin.dsl.getByType
6 |
7 | class AndroidLibraryComposeConventionPlugin : Plugin {
8 | override fun apply(target: Project) {
9 | with(target) {
10 | pluginManager.apply("com.android.library")
11 | val extension = extensions.getByType()
12 | configureAndroidCompose(extension)
13 | }
14 | }
15 |
16 | }
17 |
--------------------------------------------------------------------------------
/build-logic/convention/src/main/kotlin/AndroidLibraryConventionPlugin.kt:
--------------------------------------------------------------------------------
1 | import com.android.build.api.variant.LibraryAndroidComponentsExtension
2 | import com.android.build.gradle.LibraryExtension
3 | import template.configureFlavors
4 | import template.configureKotlinAndroid
5 | import template.configurePrintApksTask
6 | import org.gradle.api.Plugin
7 | import org.gradle.api.Project
8 | import org.gradle.api.artifacts.VersionCatalogsExtension
9 | import org.gradle.kotlin.dsl.configure
10 | import org.gradle.kotlin.dsl.dependencies
11 | import org.gradle.kotlin.dsl.getByType
12 | import org.gradle.kotlin.dsl.kotlin
13 | import template.libs
14 |
15 | class AndroidLibraryConventionPlugin : Plugin {
16 | override fun apply(target: Project) {
17 | with(target) {
18 | with(pluginManager) {
19 | apply("com.android.library")
20 | apply("org.jetbrains.kotlin.android")
21 | }
22 |
23 | extensions.configure {
24 | configureKotlinAndroid(this)
25 | defaultConfig.targetSdk = libs.findVersion("compile-sdk").get().toString().toInt()
26 | configureFlavors(this)
27 | }
28 | extensions.configure {
29 | configurePrintApksTask(this)
30 | }
31 | val libs = extensions.getByType().named("libs")
32 | configurations.configureEach {
33 | resolutionStrategy {
34 | force(libs.findLibrary("junit4").get())
35 | // Temporary workaround for https://issuetracker.google.com/174733673
36 | force("org.objenesis:objenesis:2.6")
37 | }
38 | }
39 | dependencies {
40 | add("androidTestImplementation", kotlin("test"))
41 | add("testImplementation", kotlin("test"))
42 | }
43 | }
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/build-logic/convention/src/main/kotlin/AndroidLibraryJacocoConventionPlugin.kt:
--------------------------------------------------------------------------------
1 | import com.android.build.api.variant.LibraryAndroidComponentsExtension
2 | import template.configureJacoco
3 | import org.gradle.api.Plugin
4 | import org.gradle.api.Project
5 | import org.gradle.kotlin.dsl.getByType
6 |
7 | class AndroidLibraryJacocoConventionPlugin : Plugin {
8 | override fun apply(target: Project) {
9 | with(target) {
10 | with(pluginManager) {
11 | apply("org.gradle.jacoco")
12 | apply("com.android.library")
13 | }
14 | val extension = extensions.getByType()
15 | configureJacoco(extension)
16 | }
17 | }
18 |
19 | }
20 |
--------------------------------------------------------------------------------
/build-logic/convention/src/main/kotlin/AndroidRoomConventionPlugin.kt:
--------------------------------------------------------------------------------
1 | import com.google.devtools.ksp.gradle.KspExtension
2 | import org.gradle.api.Plugin
3 | import org.gradle.api.Project
4 | import org.gradle.api.artifacts.VersionCatalogsExtension
5 | import org.gradle.api.tasks.InputDirectory
6 | import org.gradle.api.tasks.PathSensitive
7 | import org.gradle.api.tasks.PathSensitivity
8 | import org.gradle.kotlin.dsl.configure
9 | import org.gradle.kotlin.dsl.dependencies
10 | import org.gradle.kotlin.dsl.getByType
11 | import org.gradle.process.CommandLineArgumentProvider
12 | import java.io.File
13 |
14 | class AndroidRoomConventionPlugin : Plugin {
15 |
16 | override fun apply(target: Project) {
17 | with(target) {
18 | pluginManager.apply("com.google.devtools.ksp")
19 |
20 | extensions.configure {
21 | // The schemas directory contains a schema file for each version of the Room database.
22 | // This is required to enable Room auto migrations.
23 | // See https://developer.android.com/reference/kotlin/androidx/room/AutoMigration.
24 | // arg(RoomSchemaArgProvider(File(projectDir, "schemas")))
25 | }
26 |
27 | val libs = extensions.getByType().named("libs")
28 | dependencies {
29 | add("implementation", libs.findLibrary("androidx.room.runtime").get())
30 | add("implementation", libs.findLibrary("androidx.room.ktx").get())
31 | add("implementation", libs.findLibrary("androidx.room.paging").get())
32 | add("ksp", libs.findLibrary("androidx.room.compiler").get())
33 | }
34 | }
35 | }
36 |
37 | /**
38 | * https://issuetracker.google.com/issues/132245929
39 | * [Export schemas](https://developer.android.com/training/data-storage/room/migrating-db-versions#export-schemas)
40 | */
41 | class RoomSchemaArgProvider(
42 | @get:InputDirectory
43 | @get:PathSensitive(PathSensitivity.RELATIVE)
44 | val schemaDir: File,
45 | ) : CommandLineArgumentProvider {
46 | override fun asArguments() = listOf("room.schemaLocation=${schemaDir.path}")
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/build-logic/convention/src/main/kotlin/AndroidTestConventionPlugin.kt:
--------------------------------------------------------------------------------
1 | import com.android.build.gradle.TestExtension
2 | import template.configureKotlinAndroid
3 | import org.gradle.api.Plugin
4 | import org.gradle.api.Project
5 | import org.gradle.kotlin.dsl.configure
6 | import template.libs
7 |
8 | class AndroidTestConventionPlugin : Plugin {
9 | override fun apply(target: Project) {
10 | with(target) {
11 | with(pluginManager) {
12 | apply("com.android.test")
13 | apply("org.jetbrains.kotlin.android")
14 | }
15 |
16 | extensions.configure {
17 | configureKotlinAndroid(this)
18 | defaultConfig.targetSdk = libs.findVersion("compile-sdk").get().toString().toInt()
19 | }
20 | }
21 | }
22 |
23 | }
24 |
--------------------------------------------------------------------------------
/build-logic/convention/src/main/kotlin/FirebasePerfConventionPlugin.kt:
--------------------------------------------------------------------------------
1 | import org.gradle.api.Plugin
2 | import org.gradle.api.Project
3 |
4 | class FirebasePerfConventionPlugin : Plugin {
5 | override fun apply(target: Project) {
6 | with(target) {
7 | pluginManager.findPlugin("com.google.firebase.firebase-perf").apply {
8 | version = "1.4.1"
9 | }
10 | }
11 | }
12 |
13 | }
14 |
--------------------------------------------------------------------------------
/build-logic/convention/src/main/kotlin/KotlinLinterPlugin.kt:
--------------------------------------------------------------------------------
1 | import org.gradle.api.Plugin
2 | import org.gradle.api.Project
3 |
4 | class KotlinLinterPlugin : Plugin {
5 | override fun apply(target: Project) {
6 | with(target) {
7 | pluginManager.apply("org.jmailen.kotlinter")
8 | }
9 | }
10 |
11 | }
12 |
--------------------------------------------------------------------------------
/build-logic/convention/src/main/kotlin/template/AndroidCompose.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2022 The Android Open Source Project
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * https://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package template
18 |
19 | import com.android.build.api.dsl.CommonExtension
20 | import org.gradle.api.Project
21 | import org.gradle.api.artifacts.VersionCatalogsExtension
22 | import org.gradle.kotlin.dsl.dependencies
23 | import org.gradle.kotlin.dsl.getByType
24 | import java.io.File
25 |
26 | /**
27 | * Configure Compose-specific options
28 | */
29 | internal fun Project.configureAndroidCompose(
30 | commonExtension: CommonExtension<*, *, *, *, *>,
31 | ) {
32 | commonExtension.apply {
33 | buildFeatures {
34 | compose = true
35 | }
36 |
37 | composeOptions {
38 | kotlinCompilerExtensionVersion = libs.findVersion("compose-compiler").get().toString()
39 | }
40 |
41 | kotlinOptions {
42 | freeCompilerArgs = freeCompilerArgs + buildComposeMetricsParameters()
43 | }
44 |
45 | dependencies {
46 | val bom = libs.findLibrary("compose-bom").get()
47 | add("implementation", platform(bom))
48 | add("androidTestImplementation", platform(bom))
49 | }
50 | }
51 | }
52 |
53 | private fun Project.buildComposeMetricsParameters(): List {
54 | val metricParameters = mutableListOf()
55 | val enableMetricsProvider = project.providers.gradleProperty("enableComposeCompilerMetrics")
56 | val enableMetrics = (enableMetricsProvider.orNull == "true")
57 | if (enableMetrics) {
58 | val metricsFolder = File(project.buildDir, "compose-metrics")
59 | metricParameters.add("-P")
60 | metricParameters.add(
61 | "plugin:androidx.compose.compiler.plugins.kotlin:metricsDestination=" + metricsFolder.absolutePath
62 | )
63 | }
64 |
65 | val enableReportsProvider = project.providers.gradleProperty("enableComposeCompilerReports")
66 | val enableReports = (enableReportsProvider.orNull == "true")
67 | if (enableReports) {
68 | val reportsFolder = File(project.buildDir, "compose-reports")
69 | metricParameters.add("-P")
70 | metricParameters.add(
71 | "plugin:androidx.compose.compiler.plugins.kotlin:reportsDestination=" + reportsFolder.absolutePath
72 | )
73 | }
74 | return metricParameters.toList()
75 | }
76 |
--------------------------------------------------------------------------------
/build-logic/convention/src/main/kotlin/template/Flavor.kt:
--------------------------------------------------------------------------------
1 | package template
2 |
3 | import com.android.build.api.dsl.ApplicationExtension
4 | import com.android.build.api.dsl.ApplicationProductFlavor
5 | import com.android.build.api.dsl.CommonExtension
6 | import org.gradle.api.Project
7 |
8 | @Suppress("EnumEntryName", "EnumNaming")
9 | enum class FlavorDimension {
10 | contentType
11 | }
12 |
13 | // The content for the app can either come from local static data which is useful for demo
14 | // purposes, or from a production backend server which supplies up-to-date, real content.
15 | // These two product flavors reflect this behaviour.
16 | @Suppress("EnumEntryName", "EnumNaming")
17 | enum class Flavor(val dimension: FlavorDimension, val applicationIdSuffix: String? = null) {
18 | dev(FlavorDimension.contentType, ".dev"),
19 | beta(FlavorDimension.contentType, ".beta"),
20 | prod(FlavorDimension.contentType)
21 | }
22 |
23 | fun Project.configureFlavors(
24 | commonExtension: CommonExtension<*, *, *, *, *>,
25 | ) {
26 | commonExtension.apply {
27 | flavorDimensions += FlavorDimension.contentType.name
28 | productFlavors {
29 | Flavor.values().forEach {
30 | create(it.name) {
31 | dimension = it.dimension.name
32 | if (this@apply is ApplicationExtension && this is ApplicationProductFlavor) {
33 | if (it.applicationIdSuffix != null) {
34 | this.applicationIdSuffix = it.applicationIdSuffix
35 | }
36 | }
37 | val appName = buildString {
38 | append("template")
39 | if (it.applicationIdSuffix != null) {
40 | append(" - ")
41 | append(it.applicationIdSuffix.uppercase())
42 | }
43 | }
44 | resValue("string", "app_name", appName)
45 | }
46 | }
47 | }
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/build-logic/convention/src/main/kotlin/template/Jacoco.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2022 The Android Open Source Project
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * https://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package template
18 |
19 | import com.android.build.api.variant.AndroidComponentsExtension
20 | import org.gradle.api.Project
21 | import org.gradle.api.artifacts.VersionCatalogsExtension
22 | import org.gradle.api.tasks.testing.Test
23 | import org.gradle.kotlin.dsl.configure
24 | import org.gradle.kotlin.dsl.getByType
25 | import org.gradle.kotlin.dsl.register
26 | import org.gradle.kotlin.dsl.withType
27 | import org.gradle.testing.jacoco.plugins.JacocoPluginExtension
28 | import org.gradle.testing.jacoco.plugins.JacocoTaskExtension
29 | import org.gradle.testing.jacoco.tasks.JacocoReport
30 |
31 | private val coverageExclusions = listOf(
32 | // Android
33 | "**/R.class",
34 | "**/R\$*.class",
35 | "**/BuildConfig.*",
36 | "**/Manifest*.*"
37 | )
38 |
39 | internal fun Project.configureJacoco(
40 | androidComponentsExtension: AndroidComponentsExtension<*, *, *>,
41 | ) {
42 | val libs = extensions.getByType().named("libs")
43 |
44 | configure {
45 | toolVersion = libs.findVersion("jacoco").get().toString()
46 | }
47 |
48 | val jacocoTestReport = tasks.create("jacocoTestReport")
49 |
50 | androidComponentsExtension.onVariants { variant ->
51 | val testTaskName = "test${variant.name.capitalize()}UnitTest"
52 |
53 | val reportTask = tasks.register("jacoco${testTaskName.capitalize()}Report", JacocoReport::class) {
54 | dependsOn(testTaskName)
55 |
56 | reports {
57 | xml.required.set(true)
58 | html.required.set(true)
59 | }
60 |
61 | classDirectories.setFrom(
62 | fileTree("$buildDir/tmp/kotlin-classes/${variant.name}") {
63 | exclude(coverageExclusions)
64 | }
65 | )
66 |
67 | sourceDirectories.setFrom(files("$projectDir/src/main/java", "$projectDir/src/main/kotlin"))
68 | executionData.setFrom(file("$buildDir/jacoco/$testTaskName.exec"))
69 | }
70 |
71 | jacocoTestReport.dependsOn(reportTask)
72 | }
73 |
74 | tasks.withType().configureEach {
75 | configure {
76 | // Required for JaCoCo + Robolectric
77 | // https://github.com/robolectric/robolectric/issues/2230
78 | // TODO: Consider removing if not we don't add Robolectric
79 | isIncludeNoLocationClasses = true
80 |
81 | // Required for JDK 11 with the above
82 | // https://github.com/gradle/gradle/issues/5184#issuecomment-391982009
83 | excludes = listOf("jdk.internal.*")
84 | }
85 | }
86 | }
87 |
--------------------------------------------------------------------------------
/build-logic/convention/src/main/kotlin/template/KotlinAndroid.kt:
--------------------------------------------------------------------------------
1 | package template
2 |
3 | import com.android.build.api.dsl.CommonExtension
4 | import org.gradle.api.JavaVersion
5 | import org.gradle.api.Project
6 | import org.gradle.api.artifacts.VersionCatalogsExtension
7 | import org.gradle.api.plugins.ExtensionAware
8 | import org.gradle.kotlin.dsl.dependencies
9 | import org.gradle.kotlin.dsl.getByType
10 | import org.gradle.kotlin.dsl.provideDelegate
11 | import org.jetbrains.kotlin.gradle.dsl.KotlinJvmOptions
12 |
13 | /**
14 | * Configure base Kotlin with Android options
15 | */
16 | internal fun Project.configureKotlinAndroid(
17 | commonExtension: CommonExtension<*, *, *, *, *>,
18 | ) {
19 | commonExtension.apply {
20 | compileSdk = libs.findVersion("compile-sdk").get().toString().toInt()
21 |
22 | defaultConfig {
23 | minSdk = libs.findVersion("minSdk").get().toString().toInt()
24 | }
25 |
26 | compileOptions {
27 | sourceCompatibility = JavaVersion.VERSION_17
28 | targetCompatibility = JavaVersion.VERSION_17
29 | isCoreLibraryDesugaringEnabled = true
30 | }
31 |
32 | kotlinOptions {
33 | // Treat all Kotlin warnings as errors (disabled by default)
34 | // Override by setting warningsAsErrors=true in your ~/.gradle/gradle.properties
35 | val warningsAsErrors: String? by project
36 | allWarningsAsErrors = warningsAsErrors.toBoolean()
37 |
38 | freeCompilerArgs = freeCompilerArgs + listOf(
39 | "-opt-in=kotlin.RequiresOptIn",
40 | // Enable experimental coroutines APIs, including Flow
41 | "-opt-in=kotlinx.coroutines.ExperimentalCoroutinesApi",
42 | "-opt-in=kotlinx.coroutines.FlowPreview",
43 | "-opt-in=kotlin.Experimental",
44 | )
45 |
46 | // Set JVM target to 17
47 | jvmTarget = JavaVersion.VERSION_17.toString()
48 | }
49 | }
50 |
51 | dependencies {
52 | add("coreLibraryDesugaring", libs.findLibrary("android.desugarJdkLibs").get())
53 | }
54 | }
55 |
56 | fun CommonExtension<*, *, *, *, *>.kotlinOptions(block: KotlinJvmOptions.() -> Unit) {
57 | (this as ExtensionAware).extensions.configure("kotlinOptions", block)
58 | }
59 |
--------------------------------------------------------------------------------
/build-logic/convention/src/main/kotlin/template/PrintTestApks.kt:
--------------------------------------------------------------------------------
1 | package template
2 |
3 | import com.android.build.api.artifact.SingleArtifact
4 | import com.android.build.api.variant.AndroidComponentsExtension
5 | import com.android.build.api.variant.BuiltArtifactsLoader
6 | import com.android.build.api.variant.HasAndroidTest
7 | import org.gradle.api.DefaultTask
8 | import org.gradle.api.Project
9 | import org.gradle.api.file.Directory
10 | import org.gradle.api.file.DirectoryProperty
11 | import org.gradle.api.provider.ListProperty
12 | import org.gradle.api.provider.Property
13 | import org.gradle.api.tasks.Input
14 | import org.gradle.api.tasks.InputDirectory
15 | import org.gradle.api.tasks.InputFiles
16 | import org.gradle.api.tasks.Internal
17 | import org.gradle.api.tasks.TaskAction
18 | import java.io.File
19 |
20 | internal fun Project.configurePrintApksTask(extension: AndroidComponentsExtension<*, *, *>) {
21 | extension.onVariants { variant ->
22 | if (variant is HasAndroidTest) {
23 | val loader = variant.artifacts.getBuiltArtifactsLoader()
24 | val artifact = variant.androidTest?.artifacts?.get(SingleArtifact.APK)
25 | val javaSources = variant.androidTest?.sources?.java?.all
26 | val kotlinSources = variant.androidTest?.sources?.kotlin?.all
27 |
28 | val testSources = if (javaSources != null && kotlinSources != null) {
29 | javaSources.zip(kotlinSources) { javaDirs, kotlinDirs ->
30 | javaDirs + kotlinDirs
31 | }
32 | } else javaSources ?: kotlinSources
33 |
34 | if (artifact != null && testSources != null) {
35 | tasks.register(
36 | "${variant.name}PrintTestApk",
37 | PrintApkLocationTask::class.java
38 | ) {
39 | apkFolder.set(artifact)
40 | builtArtifactsLoader.set(loader)
41 | variantName.set(variant.name)
42 | sources.set(testSources)
43 | }
44 | }
45 | }
46 | }
47 | }
48 |
49 | internal abstract class PrintApkLocationTask : DefaultTask() {
50 | @get:InputDirectory
51 | abstract val apkFolder: DirectoryProperty
52 |
53 | @get:InputFiles
54 | abstract val sources: ListProperty
55 |
56 | @get:Internal
57 | abstract val builtArtifactsLoader: Property
58 |
59 | @get:Input
60 | abstract val variantName: Property
61 |
62 | @TaskAction
63 | fun taskAction() {
64 | val hasFiles = sources.orNull?.any { directory ->
65 | directory.asFileTree.files.any {
66 | it.isFile && it.parentFile.path.contains("build${File.separator}generated").not()
67 | }
68 | } ?: throw UnsupportedOperationException("Cannot check androidTest sources")
69 |
70 | // Don't print APK location if there are no androidTest source files
71 | if (!hasFiles) {
72 | return
73 | }
74 |
75 | val builtArtifacts = builtArtifactsLoader.get().load(apkFolder.get())
76 | ?: throw IllegalStateException("Cannot load APKs")
77 | if (builtArtifacts.elements.size != 1)
78 | throw IllegalArgumentException("Expected one APK !")
79 | val apk = File(builtArtifacts.elements.single().outputFile).toPath()
80 | println(apk)
81 | }
82 | }
83 |
--------------------------------------------------------------------------------
/build-logic/convention/src/main/kotlin/template/ProjectExtensions.kt:
--------------------------------------------------------------------------------
1 | package template
2 |
3 | import org.gradle.api.Project
4 | import org.gradle.api.artifacts.VersionCatalog
5 | import org.gradle.api.artifacts.VersionCatalogsExtension
6 | import org.gradle.kotlin.dsl.getByType
7 |
8 | val Project.libs
9 | get(): VersionCatalog = extensions.getByType().named("libs")
10 |
--------------------------------------------------------------------------------
/build-logic/gradle.properties:
--------------------------------------------------------------------------------
1 | # Gradle properties are not passed to included builds https://github.com/gradle/gradle/issues/2534
2 | org.gradle.parallel=true
3 | org.gradle.caching=true
4 | org.gradle.configureondemand=true
5 |
--------------------------------------------------------------------------------
/build-logic/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aslansari/android-app-template/5b914d05aed2854247669bc806d9c15adb0feff2/build-logic/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/build-logic/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Sun Oct 10 21:53:17 EDT 2021
2 | distributionBase=GRADLE_USER_HOME
3 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.1.1-bin.zip
4 | distributionPath=wrapper/dists
5 | zipStorePath=wrapper/dists
6 | zipStoreBase=GRADLE_USER_HOME
7 |
--------------------------------------------------------------------------------
/build-logic/settings.gradle.kts:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2022 The Android Open Source Project
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * https://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | dependencyResolutionManagement {
18 | repositories {
19 | google()
20 | mavenCentral()
21 | }
22 | versionCatalogs {
23 | create("libs") {
24 | from(files("../gradle/libs.versions.toml"))
25 | }
26 | }
27 | }
28 |
29 | rootProject.name = "build-logic"
30 | include(":convention")
31 |
--------------------------------------------------------------------------------
/build.gradle.kts:
--------------------------------------------------------------------------------
1 | // Top-level build file where you can add configuration options common to all sub-projects/modules.
2 | plugins {
3 | alias(libs.plugins.detekt)
4 | alias(libs.plugins.kotlinter) apply false
5 | alias(libs.plugins.com.android.application) apply false
6 | alias(libs.plugins.org.jetbrains.kotlin.android) apply false
7 | alias(libs.plugins.dagger.hilt.android) apply false
8 | alias(libs.plugins.com.android.test) apply false
9 | alias(libs.plugins.google.services) apply false
10 | alias(libs.plugins.firebase.crashlytics) apply false
11 | alias(libs.plugins.ksp) apply false
12 | }
13 |
14 | apply(from = "buildscripts/githooks.gradle")
15 | apply(from = "buildscripts/setup.gradle")
16 |
17 | tasks.register("clean", Delete::class) {
18 | delete(rootProject.buildDir)
19 | }
20 |
21 | afterEvaluate {
22 | // We install the hook at the first occasion
23 | tasks.named("clean") {
24 | dependsOn(":installGitHooks")
25 | }
26 | }
27 |
28 | // Detekt Config
29 | apply(plugin = "io.gitlab.arturbosch.detekt")
30 |
31 | detekt {
32 | config = files("${rootProject.projectDir}/config/detekt/detekt.yml")
33 | baseline = file("${rootProject.projectDir}/config/detekt/detekt.xml")
34 |
35 | reports {
36 | html.required.set(true)
37 | xml.required.set(true)
38 | txt.required.set(true)
39 | }
40 | }
41 |
42 | tasks {
43 | /**
44 | * The detektAll tasks enables parallel usage for detekt so if this project
45 | * expands to multi module support, detekt can continue to run quickly.
46 | *
47 | * https://proandroiddev.com/how-to-use-detekt-in-a-multi-module-android-project-6781937fbef2
48 | */
49 | @Suppress("UnusedPrivateMember")
50 | val detektAll by registering(io.gitlab.arturbosch.detekt.Detekt::class) {
51 | parallel = true
52 | setSource(files(projectDir))
53 | include("**/*.kt")
54 | include("**/*.kts")
55 | exclude("**/resources/**")
56 | exclude("**/build/**")
57 | config.setFrom(files("$rootDir/config/detekt/detekt.yml"))
58 | buildUponDefaultConfig = false
59 | }
60 | }
61 |
--------------------------------------------------------------------------------
/buildscripts/githooks.gradle:
--------------------------------------------------------------------------------
1 | // https://blog.sebastiano.dev/ooga-chaka-git-hooks-to-enforce-code-quality/
2 | static def isLinuxOrMacOs() {
3 | def osName = System.getProperty('os.name').toLowerCase(Locale.ROOT)
4 | return osName.contains('linux') || osName.contains('mac os') || osName.contains('macos')
5 | }
6 |
7 | task copyGitHooks(type: Copy) {
8 | description 'Copies the git hooks from /git-hooks to the .git folder.'
9 | group 'git hooks'
10 | from("${rootDir}/git-hooks/") {
11 | include '**/*.sh'
12 | rename '(.*).sh', '$1'
13 | }
14 | into "${rootDir}/.git/hooks"
15 | onlyIf { isLinuxOrMacOs() }
16 | }
17 |
18 | task installGitHooks(type: Exec) {
19 | description 'Installs the pre-commit git hooks from /git-hooks.'
20 | group 'git hooks'
21 | workingDir rootDir
22 | commandLine 'chmod'
23 | args '-R', '+x', '.git/hooks/'
24 | dependsOn copyGitHooks
25 | onlyIf { isLinuxOrMacOs() }
26 | doLast {
27 | logger.info('Git hook installed successfully.')
28 | }
29 | }
--------------------------------------------------------------------------------
/buildscripts/setup.gradle:
--------------------------------------------------------------------------------
1 | def renameConfig = [
2 | templateId : "template.name",
3 | templateName : "template",
4 | templateAppId : "template.app.id",
5 | templateMaterialThemeName: "TemplateTheme",
6 | newName : "name", // project used in plugins
7 | newPackage : "domain.yourname.app",
8 | newProjectName : "Your Project",
9 | newMaterialThemeName : "MyMaterialTheme",
10 | useHiltDependencies : true,
11 | useRoomDependencies : true,
12 | useRetrofitDependencies : true,
13 | ]
14 |
15 | task deleteSetupCode() {
16 | def workflowsFolder = "${rootDir}/.github/workflows"
17 | def buildscriptsFolder = "${rootDir}/buildscripts"
18 | def templateChangeWorkflowFile = "$workflowsFolder/template_change_test.yml"
19 | def setupGradle = "$buildscriptsFolder/setup.gradle"
20 |
21 | doLast {
22 | removeTextFromFile("${rootDir}/build.gradle.kts", "setup.gradle")
23 | delete(templateChangeWorkflowFile)
24 | delete(setupGradle)
25 | }
26 | }
27 |
28 | task renameAppPackage(type: Copy) {
29 | description "Renames the template package in the app module."
30 | group null
31 |
32 | def newPackageAsDirectory = renameConfig.newPackage.replaceAll("\\.", "/")
33 | def startingDirectory = "${rootDir}/app/src/main/java/${renameConfig.templateName}"
34 | def endingDirectory = "${rootDir}/app/src/main/java/${newPackageAsDirectory}"
35 |
36 | from(startingDirectory)
37 | into(endingDirectory)
38 |
39 | // Replace package statements
40 | filter { line ->
41 | line.replaceAll(
42 | "package ${renameConfig.templateName}",
43 | "package ${renameConfig.newPackage}"
44 | )
45 | }
46 |
47 | // Replace import statements
48 | filter { line ->
49 | line.replaceAll(
50 | "import ${renameConfig.templateName}",
51 | "import ${renameConfig.newPackage}"
52 | )
53 | }
54 |
55 | // Replace Theme references. We can just replace on name,
56 | // which covers both imports and function calls.
57 | filter { line ->
58 | line.replaceAll(
59 | "${renameConfig.templateMaterialThemeName}",
60 | "${renameConfig.newMaterialThemeName}"
61 | )
62 | }
63 |
64 | doLast {
65 | delete(startingDirectory)
66 | }
67 | }
68 |
69 | task replaceTemplateReferences {
70 | description "Replaces references to template in various files."
71 | group null
72 |
73 | doLast {
74 | replaceTextInFile(
75 | "${rootDir}/app/src/main/AndroidManifest.xml",
76 | "${renameConfig.templateName}.MainActivity",
77 | "${renameConfig.newPackage}.MainActivity",
78 | )
79 |
80 | replaceTextInFile(
81 | "${rootDir}/app/build.gradle.kts",
82 | "namespace = \"${renameConfig.templateName}\"",
83 | "namespace = \"${renameConfig.newPackage}\"",
84 | )
85 |
86 | replaceTextInFile(
87 | "${rootDir}/app/build.gradle.kts",
88 | "applicationId = \"${renameConfig.templateAppId}\"",
89 | "applicationId = \"${renameConfig.newPackage}\"",
90 | )
91 |
92 | replaceTextInFile(
93 | "${rootDir}/app/build.gradle.kts",
94 | "id\\(\"${renameConfig.templateId}",
95 | "id\\(\"${renameConfig.newName}",
96 | )
97 |
98 | replaceTextInFile(
99 | "${rootDir}/settings.gradle.kts",
100 | "rootProject.name = \"${renameConfig.templateName}\"",
101 | "rootProject.name = \"${renameConfig.newProjectName}\"",
102 | )
103 |
104 | replaceTextInFile(
105 | "${rootDir}/build-logic/convention/src/main/kotlin/template/Flavor.kt",
106 | "append\\(\"${renameConfig.templateName}\"",
107 | "append\\(\"${renameConfig.newProjectName}\"",
108 | )
109 |
110 | replaceTextInFile(
111 | "${rootDir}/build-logic/convention/build.gradle.kts",
112 | "id = \"${renameConfig.templateId}",
113 | "id = \"${renameConfig.newName}",
114 | )
115 |
116 | replaceTextInFile(
117 | "${rootDir}/build-logic/convention/build.gradle.kts",
118 | "group = \"${renameConfig.templateName}",
119 | "group = \"${renameConfig.newPackage}",
120 | )
121 |
122 | replaceTextInFile(
123 | "${rootDir}/build-logic/convention/src/main/kotlin/AndroidFeatureConventionPlugin.kt",
124 | "apply\\(\"${renameConfig.templateId}",
125 | "apply\\(\"${renameConfig.newName}",
126 | )
127 |
128 | replaceTextInFile(
129 | "${rootDir}/build-logic/convention/src/main/kotlin/AndroidFeatureConventionPlugin.kt",
130 | "apply\\(\"${renameConfig.templateName}",
131 | "apply\\(\"${renameConfig.newPackage}",
132 | )
133 | }
134 | }
135 |
136 | task keepOrRemoveDependencies {
137 | description "Keeps or removes certain dependencies defined in renameConfig."
138 | group null
139 |
140 | doLast {
141 | def filesWithDependencies = [
142 | "${rootDir}/build.gradle.kts",
143 | "${rootDir}/app/build.gradle.kts",
144 | "${rootDir}/gradle/libs.versions.toml",
145 | ]
146 |
147 | filesWithDependencies.each { fileName ->
148 | if (renameConfig.useHiltDependencies != true) {
149 | removeTextFromFile(fileName, "hilt")
150 | }
151 |
152 | if (renameConfig.useRoomDependencies != true) {
153 | removeTextFromFile(fileName, "room")
154 | }
155 |
156 | if (renameConfig.useRetrofitDependencies != true) {
157 | removeTextFromFile(fileName, "retrofit")
158 | removeTextFromFile(fileName, "moshi")
159 | }
160 | }
161 | }
162 | }
163 |
164 | task renameTemplate {
165 | description "Runs all of the necessary template setup tasks based on the renameConfig."
166 | group "Template Setup"
167 |
168 | dependsOn(
169 | keepOrRemoveDependencies,
170 | replaceTemplateReferences,
171 | renameAppPackage,
172 | deleteSetupCode,
173 | )
174 |
175 | doLast {
176 | exec {
177 | // After all setup changes happen, run a `git add` so
178 | // folks can just immediately commit and push if they wish.
179 | commandLine "git", "add", "${rootDir}/."
180 | }
181 | }
182 | }
183 |
184 | /**
185 | * Replaces all instances of [text] in a given [fileName].
186 | */
187 | static def replaceTextInFile(fileName, originalText, newText) {
188 | def file = new File(fileName)
189 |
190 | file.text = file.text.replaceAll(originalText, newText)
191 | }
192 |
193 | /**
194 | * Removes all lines from the given fileName that contain some supplied text.
195 | */
196 | static def removeTextFromFile(fileName, text) {
197 | def file = new File(fileName)
198 | List fileLines = file.readLines()
199 | file.text = ""
200 | fileLines.each { line ->
201 | if (!line.contains(text)) {
202 | file.append(line)
203 | file.append("\n")
204 | }
205 | }
206 | }
207 |
--------------------------------------------------------------------------------
/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 |
42 | output-reports:
43 | active: true
44 | exclude:
45 | # - 'TxtOutputReport'
46 | # - 'XmlOutputReport'
47 | # - 'HtmlOutputReport'
48 |
49 | comments:
50 | active: true
51 | excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/jsTest/**', '**/iosTest/**']
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 | UndocumentedPublicClass:
66 | active: false
67 | searchInNestedClass: true
68 | searchInInnerClass: true
69 | searchInInnerObject: true
70 | searchInInnerInterface: true
71 | UndocumentedPublicFunction:
72 | active: false
73 | UndocumentedPublicProperty:
74 | active: false
75 |
76 | complexity:
77 | active: true
78 | ComplexCondition:
79 | active: true
80 | threshold: 4
81 | ComplexInterface:
82 | active: false
83 | threshold: 10
84 | includeStaticDeclarations: false
85 | includePrivateDeclarations: false
86 | ComplexMethod:
87 | active: true
88 | threshold: 15
89 | ignoreSingleWhenExpression: false
90 | ignoreSimpleWhenEntries: false
91 | ignoreNestingFunctions: false
92 | nestingFunctions: ['run', 'let', 'apply', 'with', 'also', 'use', 'forEach', 'isNotNull', 'ifNull']
93 | LabeledExpression:
94 | active: false
95 | ignoredLabels: []
96 | LargeClass:
97 | active: true
98 | threshold: 600
99 | LongMethod:
100 | active: true
101 | threshold: 60
102 | LongParameterList:
103 | active: true
104 | functionThreshold: 6
105 | constructorThreshold: 7
106 | ignoreDefaultParameters: false
107 | ignoreDataClasses: true
108 | ignoreAnnotated: []
109 | MethodOverloading:
110 | active: false
111 | threshold: 6
112 | NamedArguments:
113 | active: false
114 | threshold: 3
115 | NestedBlockDepth:
116 | active: true
117 | threshold: 4
118 | ReplaceSafeCallChainWithRun:
119 | active: false
120 | StringLiteralDuplication:
121 | active: false
122 | excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/jsTest/**', '**/iosTest/**']
123 | threshold: 3
124 | ignoreAnnotation: true
125 | excludeStringsWithLessThan5Characters: true
126 | ignoreStringsRegex: '$^'
127 | TooManyFunctions:
128 | active: true
129 | excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/jsTest/**', '**/iosTest/**']
130 | thresholdInFiles: 11
131 | thresholdInClasses: 11
132 | thresholdInInterfaces: 11
133 | thresholdInObjects: 11
134 | thresholdInEnums: 11
135 | ignoreDeprecated: false
136 | ignorePrivate: false
137 | ignoreOverridden: false
138 |
139 | coroutines:
140 | active: true
141 | GlobalCoroutineUsage:
142 | active: false
143 | RedundantSuspendModifier:
144 | active: false
145 | SleepInsteadOfDelay:
146 | active: false
147 | SuspendFunWithFlowReturnType:
148 | active: false
149 |
150 | empty-blocks:
151 | active: true
152 | EmptyCatchBlock:
153 | active: true
154 | allowedExceptionNameRegex: '_|(ignore|expected).*'
155 | EmptyClassBlock:
156 | active: true
157 | EmptyDefaultConstructor:
158 | active: true
159 | EmptyDoWhileBlock:
160 | active: true
161 | EmptyElseBlock:
162 | active: true
163 | EmptyFinallyBlock:
164 | active: true
165 | EmptyForBlock:
166 | active: true
167 | EmptyFunctionBlock:
168 | active: true
169 | ignoreOverridden: false
170 | EmptyIfBlock:
171 | active: true
172 | EmptyInitBlock:
173 | active: true
174 | EmptyKtFile:
175 | active: true
176 | EmptySecondaryConstructor:
177 | active: true
178 | EmptyTryBlock:
179 | active: true
180 | EmptyWhenBlock:
181 | active: true
182 | EmptyWhileBlock:
183 | active: true
184 |
185 | exceptions:
186 | active: true
187 | ExceptionRaisedInUnexpectedLocation:
188 | active: true
189 | methodNames: [toString, hashCode, equals, finalize]
190 | InstanceOfCheckForException:
191 | active: false
192 | excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/jsTest/**', '**/iosTest/**']
193 | NotImplementedDeclaration:
194 | active: false
195 | ObjectExtendsThrowable:
196 | active: false
197 | PrintStackTrace:
198 | active: true
199 | RethrowCaughtException:
200 | active: true
201 | ReturnFromFinally:
202 | active: true
203 | ignoreLabeled: false
204 | SwallowedException:
205 | active: true
206 | ignoredExceptionTypes:
207 | - InterruptedException
208 | - NumberFormatException
209 | - ParseException
210 | - MalformedURLException
211 | allowedExceptionNameRegex: '_|(ignore|expected).*'
212 | ThrowingExceptionFromFinally:
213 | active: true
214 | ThrowingExceptionInMain:
215 | active: false
216 | ThrowingExceptionsWithoutMessageOrCause:
217 | active: true
218 | excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/jsTest/**', '**/iosTest/**']
219 | exceptions:
220 | - IllegalArgumentException
221 | - IllegalStateException
222 | - IOException
223 | ThrowingNewInstanceOfSameException:
224 | active: true
225 | TooGenericExceptionCaught:
226 | active: true
227 | excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/jsTest/**', '**/iosTest/**']
228 | exceptionNames:
229 | - ArrayIndexOutOfBoundsException
230 | - Error
231 | - Exception
232 | - IllegalMonitorStateException
233 | - NullPointerException
234 | - IndexOutOfBoundsException
235 | - RuntimeException
236 | - Throwable
237 | allowedExceptionNameRegex: '_|(ignore|expected).*'
238 | TooGenericExceptionThrown:
239 | active: true
240 | exceptionNames:
241 | - Error
242 | - Exception
243 | - Throwable
244 | - RuntimeException
245 |
246 | naming:
247 | active: true
248 | ClassNaming:
249 | active: true
250 | excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/jsTest/**', '**/iosTest/**']
251 | classPattern: '[A-Z][a-zA-Z0-9]*'
252 | ConstructorParameterNaming:
253 | active: true
254 | excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/jsTest/**', '**/iosTest/**']
255 | parameterPattern: '[a-z][A-Za-z0-9]*'
256 | privateParameterPattern: '[a-z][A-Za-z0-9]*'
257 | excludeClassPattern: '$^'
258 | ignoreOverridden: true
259 | EnumNaming:
260 | active: true
261 | excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/jsTest/**', '**/iosTest/**']
262 | enumEntryPattern: '[A-Z][_a-zA-Z0-9]*'
263 | ForbiddenClassName:
264 | active: false
265 | excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/jsTest/**', '**/iosTest/**']
266 | forbiddenName: []
267 | FunctionMaxLength:
268 | active: false
269 | excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/jsTest/**', '**/iosTest/**']
270 | maximumFunctionNameLength: 30
271 | FunctionMinLength:
272 | active: false
273 | excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/jsTest/**', '**/iosTest/**']
274 | minimumFunctionNameLength: 3
275 | FunctionNaming:
276 | active: true
277 | excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/jsTest/**', '**/iosTest/**']
278 | functionPattern: '([a-z][a-zA-Z0-9]*)|(`.*`)'
279 | excludeClassPattern: '$^'
280 | ignoreOverridden: true
281 | ignoreAnnotated: ['Composable']
282 | FunctionParameterNaming:
283 | active: true
284 | excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/jsTest/**', '**/iosTest/**']
285 | parameterPattern: '[a-z][A-Za-z0-9]*'
286 | excludeClassPattern: '$^'
287 | ignoreOverridden: true
288 | InvalidPackageDeclaration:
289 | active: false
290 | excludes: ['*.kts']
291 | rootPackage: ''
292 | MatchingDeclarationName:
293 | active: true
294 | mustBeFirst: true
295 | MemberNameEqualsClassName:
296 | active: true
297 | ignoreOverridden: true
298 | NoNameShadowing:
299 | active: false
300 | NonBooleanPropertyPrefixedWithIs:
301 | active: false
302 | excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/jsTest/**', '**/iosTest/**']
303 | ObjectPropertyNaming:
304 | active: true
305 | excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/jsTest/**', '**/iosTest/**']
306 | constantPattern: '[A-Za-z][_A-Za-z0-9]*'
307 | propertyPattern: '[A-Za-z][_A-Za-z0-9]*'
308 | privatePropertyPattern: '(_)?[A-Za-z][_A-Za-z0-9]*'
309 | PackageNaming:
310 | active: true
311 | excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/jsTest/**', '**/iosTest/**']
312 | packagePattern: '[a-z]+(\.[a-z][A-Za-z0-9]*)*'
313 | TopLevelPropertyNaming:
314 | active: true
315 | excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/jsTest/**', '**/iosTest/**']
316 | constantPattern: '[A-Z][_A-Z0-9]*'
317 | propertyPattern: '[A-Za-z][_A-Za-z0-9]*'
318 | privatePropertyPattern: '_?[A-Za-z][_A-Za-z0-9]*'
319 | VariableMaxLength:
320 | active: false
321 | excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/jsTest/**', '**/iosTest/**']
322 | maximumVariableNameLength: 64
323 | VariableMinLength:
324 | active: false
325 | excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/jsTest/**', '**/iosTest/**']
326 | minimumVariableNameLength: 1
327 | VariableNaming:
328 | active: true
329 | excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/jsTest/**', '**/iosTest/**']
330 | variablePattern: '[a-z][A-Za-z0-9]*'
331 | privateVariablePattern: '(_)?[a-z][A-Za-z0-9]*'
332 | excludeClassPattern: '$^'
333 | ignoreOverridden: true
334 |
335 | performance:
336 | active: true
337 | ArrayPrimitive:
338 | active: true
339 | ForEachOnRange:
340 | active: true
341 | excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/jsTest/**', '**/iosTest/**']
342 | SpreadOperator:
343 | active: true
344 | excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/jsTest/**', '**/iosTest/**']
345 | UnnecessaryTemporaryInstantiation:
346 | active: true
347 |
348 | potential-bugs:
349 | active: true
350 | CastToNullableType:
351 | active: false
352 | Deprecation:
353 | active: false
354 | DontDowncastCollectionTypes:
355 | active: false
356 | DoubleMutabilityForCollection:
357 | active: false
358 | DuplicateCaseInWhenExpression:
359 | active: true
360 | EqualsAlwaysReturnsTrueOrFalse:
361 | active: true
362 | EqualsWithHashCodeExist:
363 | active: true
364 | ExitOutsideMain:
365 | active: false
366 | ExplicitGarbageCollectionCall:
367 | active: true
368 | HasPlatformType:
369 | active: false
370 | IgnoredReturnValue:
371 | active: false
372 | restrictToAnnotatedMethods: true
373 | returnValueAnnotations: ['*.CheckReturnValue', '*.CheckResult']
374 | ImplicitDefaultLocale:
375 | active: true
376 | ImplicitUnitReturnType:
377 | active: false
378 | allowExplicitReturnType: true
379 | InvalidRange:
380 | active: true
381 | IteratorHasNextCallsNextMethod:
382 | active: true
383 | IteratorNotThrowingNoSuchElementException:
384 | active: true
385 | LateinitUsage:
386 | active: false
387 | excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/jsTest/**', '**/iosTest/**']
388 | excludeAnnotatedProperties: []
389 | ignoreOnClassesPattern: ''
390 | MapGetWithNotNullAssertionOperator:
391 | active: false
392 | MissingWhenCase:
393 | active: true
394 | allowElseExpression: true
395 | NullableToStringCall:
396 | active: false
397 | RedundantElseInWhen:
398 | active: true
399 | UnconditionalJumpStatementInLoop:
400 | active: false
401 | UnnecessaryNotNullOperator:
402 | active: true
403 | UnnecessarySafeCall:
404 | active: true
405 | UnreachableCatchBlock:
406 | active: false
407 | UnreachableCode:
408 | active: true
409 | UnsafeCallOnNullableType:
410 | active: true
411 | UnsafeCast:
412 | active: true
413 | UnusedUnaryOperator:
414 | active: false
415 | UselessPostfixExpression:
416 | active: false
417 | WrongEqualsTypeParameter:
418 | active: true
419 |
420 | style:
421 | active: true
422 | ClassOrdering:
423 | active: false
424 | CollapsibleIfStatements:
425 | active: false
426 | DataClassContainsFunctions:
427 | active: false
428 | conversionFunctionPrefix: 'to'
429 | DataClassShouldBeImmutable:
430 | active: false
431 | DestructuringDeclarationWithTooManyEntries:
432 | active: false
433 | maxDestructuringEntries: 3
434 | EqualsNullCall:
435 | active: true
436 | EqualsOnSignatureLine:
437 | active: false
438 | ExplicitCollectionElementAccessMethod:
439 | active: false
440 | ExplicitItLambdaParameter:
441 | active: false
442 | ExpressionBodySyntax:
443 | active: false
444 | includeLineWrapping: false
445 | ForbiddenComment:
446 | active: false
447 | values: ['TODO:', 'FIXME:', 'STOPSHIP:']
448 | allowedPatterns: ''
449 | ForbiddenImport:
450 | active: false
451 | imports: []
452 | forbiddenPatterns: ''
453 | ForbiddenMethodCall:
454 | active: false
455 | methods: ['kotlin.io.println', 'kotlin.io.print']
456 | ForbiddenPublicDataClass:
457 | active: true
458 | excludes: ['**']
459 | ignorePackages: ['*.internal', '*.internal.*']
460 | ForbiddenVoid:
461 | active: false
462 | ignoreOverridden: false
463 | ignoreUsageInGenerics: false
464 | FunctionOnlyReturningConstant:
465 | active: true
466 | ignoreOverridableFunction: true
467 | ignoreActualFunction: true
468 | excludedFunctions: 'describeContents'
469 | excludeAnnotatedFunction: ['dagger.Provides']
470 | LibraryCodeMustSpecifyReturnType:
471 | active: true
472 | excludes: ['**']
473 | LibraryEntitiesShouldNotBePublic:
474 | active: true
475 | excludes: ['**']
476 | LoopWithTooManyJumpStatements:
477 | active: true
478 | maxJumpCount: 1
479 | MagicNumber:
480 | active: true
481 | excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/jsTest/**', '**/iosTest/**', '**/build-logic/**']
482 | ignoreNumbers: ['-1', '0', '1', '2']
483 | ignoreHashCodeFunction: true
484 | ignorePropertyDeclaration: false
485 | ignoreLocalVariableDeclaration: false
486 | ignoreConstantDeclaration: true
487 | ignoreCompanionObjectPropertyDeclaration: true
488 | ignoreAnnotation: false
489 | ignoreNamedArgument: true
490 | ignoreEnums: false
491 | ignoreRanges: false
492 | ignoreExtensionFunctions: true
493 | ignoreAnnotated: ['Preview']
494 | MandatoryBracesIfStatements:
495 | active: false
496 | MandatoryBracesLoops:
497 | active: false
498 | MaxLineLength:
499 | active: true
500 | maxLineLength: 120
501 | excludePackageStatements: true
502 | excludeImportStatements: true
503 | excludeCommentStatements: false
504 | MayBeConst:
505 | active: true
506 | ModifierOrder:
507 | active: true
508 | MultilineLambdaItParameter:
509 | active: false
510 | NestedClassesVisibility:
511 | active: true
512 | NewLineAtEndOfFile:
513 | active: true
514 | NoTabs:
515 | active: false
516 | ObjectLiteralToLambda:
517 | active: false
518 | OptionalAbstractKeyword:
519 | active: true
520 | OptionalUnit:
521 | active: false
522 | OptionalWhenBraces:
523 | active: false
524 | PreferToOverPairSyntax:
525 | active: false
526 | ProtectedMemberInFinalClass:
527 | active: true
528 | RedundantExplicitType:
529 | active: false
530 | RedundantHigherOrderMapUsage:
531 | active: false
532 | RedundantVisibilityModifierRule:
533 | active: false
534 | ReturnCount:
535 | active: true
536 | max: 2
537 | excludedFunctions: 'equals'
538 | excludeLabeled: false
539 | excludeReturnFromLambda: true
540 | excludeGuardClauses: false
541 | SafeCast:
542 | active: true
543 | SerialVersionUIDInSerializableClass:
544 | active: true
545 | SpacingBetweenPackageAndImports:
546 | active: false
547 | ThrowsCount:
548 | active: true
549 | max: 4
550 | TrailingWhitespace:
551 | active: false
552 | UnderscoresInNumericLiterals:
553 | active: false
554 | acceptableDecimalLength: 5
555 | UnnecessaryAbstractClass:
556 | active: true
557 | excludeAnnotatedClasses: ['dagger.Module']
558 | UnnecessaryAnnotationUseSiteTarget:
559 | active: false
560 | UnnecessaryApply:
561 | active: true
562 | UnnecessaryFilter:
563 | active: false
564 | UnnecessaryInheritance:
565 | active: true
566 | UnnecessaryLet:
567 | active: false
568 | UnnecessaryParentheses:
569 | active: false
570 | UntilInsteadOfRangeTo:
571 | active: false
572 | UnusedImports:
573 | active: false
574 | UnusedPrivateClass:
575 | active: true
576 | UnusedPrivateMember:
577 | active: true
578 | allowedNames: '(_|ignored|expected|serialVersionUID)'
579 | ignoreAnnotated: ['Preview']
580 | UseArrayLiteralsInAnnotations:
581 | active: false
582 | UseCheckNotNull:
583 | active: false
584 | UseCheckOrError:
585 | active: false
586 | UseDataClass:
587 | active: false
588 | excludeAnnotatedClasses: []
589 | allowVars: false
590 | UseEmptyCounterpart:
591 | active: false
592 | UseIfEmptyOrIfBlank:
593 | active: false
594 | UseIfInsteadOfWhen:
595 | active: false
596 | UseIsNullOrEmpty:
597 | active: false
598 | UseOrEmpty:
599 | active: false
600 | UseRequire:
601 | active: false
602 | UseRequireNotNull:
603 | active: false
604 | UselessCallOnNotNull:
605 | active: true
606 | UtilityClassWithPublicConstructor:
607 | active: true
608 | VarCouldBeVal:
609 | active: true
610 | WildcardImport:
611 | active: true
612 | excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/jsTest/**', '**/iosTest/**']
613 | excludeImports: ['java.util.*', 'kotlinx.android.synthetic.*']
614 |
--------------------------------------------------------------------------------
/documentation/GitHooks.md:
--------------------------------------------------------------------------------
1 | # Git Hooks
2 |
3 | This project has some Git hooks included inside the [git-hooks](/git-hooks) folder. These hooks can be installed automatically via the Gradle commands `./gradlew copyGitHooks` and `./gradlew installGitHooks`. You can find these commands in [this Gradle file](/buildscripts/githooks.gradle), but it's also good to know that the hooks are installed automatically just by running a `clean` task. Thanks to [Sebastiano's blog post](https://blog.sebastiano.dev/ooga-chaka-git-hooks-to-enforce-code-quality/) for that inspiration.
4 |
5 | ## Pre-Commit
6 |
7 | There is a [pre-commit](/git-hooks/pre-commit.sh) hook that will automatically run Ktlint formatting over any modified Kotlin files. This way you can just commit your code and trust that formatting happens behind the scenes, without having to consciously worry about it.
8 |
9 | ## Pre-Push
10 |
11 | There is a [pre-push](/git-hooks/pre-push.sh) hook that will run static analysis checks before any code is pushed up to the remote repository. This way, if you have any code smells, you can be alerted right away without waiting for the GitHub Actions to fail.
--------------------------------------------------------------------------------
/documentation/GitHubActions.md:
--------------------------------------------------------------------------------
1 | # GitHub Actions
2 |
3 | This project has [GitHub Actions](https://github.com/features/actions) workflows to validate our code for us automatically. The project currently uses two workflows.
4 |
5 | ## Android Build
6 |
7 | The [Android Build](/.github/workflows/android_build.yml) workflow automates the core checks for the repository: compile, unit tests, lint checks. This is set to run on every push.
8 |
9 | ## Android UI Tests
10 |
11 | The [Android UI Tests](/.github/workflows/android_ui_tests.yml) is a separate workflow that is set to only run on pull request. This is because UI tests are slow and take up a lot of resources, so we only want to validate them when we're ready to merge changes into our base branch.
12 |
--------------------------------------------------------------------------------
/documentation/StaticAnalysis.md:
--------------------------------------------------------------------------------
1 | # Static Analysis
2 |
3 | This project leverages static analysis to ensure that the codebase meets certain standards that can be verified through automation. Two of these libraries are Detekt and Ktlint.
4 |
5 | ## Detekt
6 |
7 | [Detekt](https://github.com/detekt/detekt) is a static analysis tool that checks for code smells. Examples include magic numbers, complicated conditionals, long methods, long parameter lists, and so much more. It is highly configurable, and if you choose to turn off any checks or customize thresholds you can do so in the [config file](/config/detekt/detekt.yml).
8 |
9 | To run a detekt validation, use the one of the following Gradle commands:
10 |
11 | ```
12 | ./gradlew detekt # Runs over each module synchronously
13 | ./gradlew detektAll # Runs over each module in parallel.
14 | ```
15 |
16 | ## Ktlint
17 |
18 | [Ktlint](https://github.com/pinterest/ktlint) is a static analysis tool from Pinterest that prevents bike shedding when it comes to code formatting. It also comes with a Gradle task to automatically format your entire codebase, if it can. The benefit of a tool like this is to ensure everyone on the team will have code formatted the same way, and there's no debating around white spaces, indentation, imports, etc.
19 |
20 | We use the [Kotlinter](https://github.com/jeremymailen/kotlinter-gradle) Ktlint Gradle plugin in this project.
21 |
22 | The following Gradle commands can be helpful:
23 |
24 | ```
25 | // Will format the codebase
26 | ./gradlew formatKotlin
27 |
28 | // Will check if everything is formatted correctly
29 | ./gradlew lintKotlin
30 | ```
--------------------------------------------------------------------------------
/git-hooks/pre-commit.sh:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 |
3 | ######## KTLINT-GRADLE HOOK START ########
4 |
5 | CHANGED_FILES="$(git --no-pager diff --name-status --no-color --cached | awk '$1 != "D" && $2 ~ /\.kts|\.kt/ { print $2}')"
6 |
7 | if [ -z "$CHANGED_FILES" ]; then
8 | echo "No Kotlin staged files."
9 | exit 0
10 | fi;
11 |
12 | echo "Running ktlint over these files:"
13 | echo "$CHANGED_FILES"
14 |
15 | ./gradlew --quiet formatKotlin -PinternalKtlintGitFilter="$CHANGED_FILES"
16 |
17 | echo "Completed ktlint run."
18 |
19 | echo "$CHANGED_FILES" | while read -r file; do
20 | if [ -f $file ]; then
21 | git add $file
22 | fi
23 | done
24 |
25 | echo "Completed ktlint hook."
26 | ######## KTLINT-GRADLE HOOK END ########
--------------------------------------------------------------------------------
/git-hooks/pre-push.sh:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 |
3 | echo "Running static analysis."
4 |
5 | ./gradlew lintKotlin
6 | ./gradlew detektAll
--------------------------------------------------------------------------------
/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=-Xmx6g -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/libs.versions.toml:
--------------------------------------------------------------------------------
1 | [versions]
2 | # Project
3 | minSdk = "21"
4 | compile-sdk = "34"
5 | compose-compiler = "1.5.7"
6 | kotlin = "1.9.21"
7 |
8 | # Plugins
9 | android-gradle-plugin = "8.2.1"
10 | com-android-test = "8.2.1"
11 | firebase-crashlytics-plugin = "2.9.9"
12 | gms-plugin = "4.4.1"
13 | ksp-plugin = "1.9.21-1.0.16"
14 | dagger-plugin = "2.51"
15 | detekt-plugin = "1.22.0"
16 | kotlinter-plugin = "3.14.0"
17 |
18 | # Libraries
19 | accompanist = "0.32.0"
20 | andrdoid-desugar-jdk-libs = "2.0.4"
21 | androidx-activity = "1.8.2"
22 | androidx-appcompat = "1.6.1"
23 | androidx-browser = "1.4.0"
24 | androidx-core = "1.9.0"
25 | androidx-splashscreen = "1.0.0"
26 | androidx-datastore = "1.0.0"
27 | androidx-espresso = "3.5.0"
28 | androidx-lifecycle = "2.6.0-alpha05"
29 | androidx-test = "1.1.5"
30 | compose-bom = "2024.03.00"
31 | compose-material3 = "1.1.0-alpha06"
32 | compose-runtime-tracing = "1.0.0-alpha01"
33 | hilt-navigation-compose = "1.0.0"
34 | firebaseBom = "31.2.0"
35 | espresso = "3.5.1"
36 | junit = "4.13.2"
37 | ktx-core = "1.12.0"
38 | leakCanary = "2.10"
39 | lifecycle = "2.7.0"
40 | material = "1.11.0"
41 | moshi = "1.15.0"
42 | retrofit = "2.9.0"
43 | room = "2.6.1"
44 |
45 | [libraries]
46 | accompanist-permissions = { group = "com.google.accompanist", name = "accompanist-permissions", version.ref = "accompanist" }
47 | android-desugarJdkLibs = { group = "com.android.tools", name = "desugar_jdk_libs", version.ref = "andrdoid-desugar-jdk-libs" }
48 | android-material = { group = "com.google.android.material", name = "material", version.ref = "material" }
49 | androidx-activity-compose = { group = "androidx.activity", name = "activity-compose", version.ref = "androidx-activity" }
50 | androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "ktx-core" }
51 | androidx-appcompat = { group = "androidx.appcompat", name = "appcompat", version.ref = "androidx-appcompat" }
52 | androidx-lifecycle-runtime-ktx = { group = "androidx.lifecycle", name = "lifecycle-runtime-ktx", version.ref = "lifecycle" }
53 | androidx-room-compiler = { group = "androidx.room", name = "room-compiler", version.ref = "room" }
54 | androidx-room-runtime = { group = "androidx.room", name = "room-runtime", version.ref = "room" }
55 | androidx-room-paging = { group = "androidx.room", name = "room-paging", version.ref = "room" }
56 | androidx-room-ktx = { group = "androidx.room", name = "room-ktx", version.ref = "room" }
57 | androidx-room-testing = { group = "androidx.room", name = "room-testing", version.ref = "room" }
58 | androidx-test-espresso-core = { group = "androidx.test.espresso", name = "espresso-core", version.ref = "espresso" }
59 | androidx-test-junit = { group = "androidx.test.ext", name = "junit", version.ref = "androidx-test" }
60 | compose-bom = { group = "androidx.compose", name = "compose-bom", version.ref = "compose-bom" }
61 | compose-material-icons-extended = { group = "androidx.compose.material", name = "material-icons-extended" }
62 | compose-material = { group = "androidx.compose.material3", name = "material3" }
63 | compose-ui = { group = "androidx.compose.ui", name = "ui" }
64 | compose-ui-test-junit = { group = "androidx.compose.ui", name = "ui-test-junit4" }
65 | compose-ui-test-manifest = { group = "androidx.compose.ui", name = "ui-test-manifest" }
66 | compose-ui-tooling = { group = "androidx.compose.ui", name = "ui-tooling" }
67 | compose-ui-tooling-preview = { group = "androidx.compose.ui", name = "ui-tooling-preview" }
68 | compose-ui-util = { group = "androidx.compose.ui", name = "ui-util" }
69 | hilt-android-core = { module = "com.google.dagger:hilt-android", version.ref = "dagger-plugin" }
70 | hilt-android-compiler = { module = "com.google.dagger:hilt-android-compiler", version.ref = "dagger-plugin" }
71 | hilt-android-testing = { module = "com.google.dagger:hilt-android-testing", version.ref = "dagger-plugin" }
72 | hilt-compiler = { module = "com.google.dagger:hilt-compiler", version.ref = "dagger-plugin" }
73 | junit4 = { module = "junit:junit", version.ref = "junit" }
74 | leakcanary = { module = "com.squareup.leakcanary:leakcanary-android", version.ref = "leakCanary" }
75 | moshi-kotlin-core = { module = "com.squareup.moshi:moshi-kotlin", version.ref = "moshi" }
76 | moshi-kotlin-codegen = { module = "com.squareup.moshi:moshi-kotlin-codegen", version.ref = "moshi" }
77 | retrofit = { module = "com.squareup.retrofit2:retrofit", version.ref = "retrofit" }
78 | retrofit-converter-moshi = { module = "com.squareup.retrofit2:converter-moshi", version.ref = "retrofit" }
79 |
80 | # Dependencies of the included build-logic
81 | android-gradlePlugin = { group = "com.android.tools.build", name = "gradle", version.ref = "android-gradle-plugin" }
82 | kotlin-gradlePlugin = { group = "org.jetbrains.kotlin", name = "kotlin-gradle-plugin", version.ref = "kotlin" }
83 | firebase-crashlytics-gradlePlugin = { group = "com.google.firebase", name = "firebase-crashlytics-gradle", version.ref = "firebase-crashlytics-plugin" }
84 | ksp-gradlePlugin = { group = "com.google.devtools.ksp", name = "com.google.devtools.ksp.gradle.plugin", version.ref = "ksp-plugin" }
85 |
86 | [plugins]
87 | com-android-application = { id = "com.android.application", version.ref = "android-gradle-plugin" }
88 | com-android-library = { id = "com.android.library", version.ref = "android-gradle-plugin" }
89 | org-jetbrains-kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" }
90 | dagger-hilt-android = { id = "com.google.dagger.hilt.android", version.ref = "dagger-plugin" }
91 | google-services = { id = "com.google.gms.google-services", version.ref = "gms-plugin" }
92 | firebase-crashlytics = { id = "com.google.firebase.crashlytics", version.ref = "firebase-crashlytics-plugin" }
93 | com-android-test = { id = "com.android.test", version.ref = "com-android-test" }
94 | detekt = { id = "io.gitlab.arturbosch.detekt", version.ref = "detekt-plugin" }
95 | kotlinter = { id = "org.jmailen.kotlinter", version.ref = "kotlinter-plugin" }
96 | ksp = { id = "com.google.devtools.ksp", version.ref = "ksp-plugin" }
97 |
98 | convention-android-application = { id = "convention.android.application", version = "unspecified" }
99 | convention-android-application-compose = { id = "convention.android.application.compose", version = "unspecified" }
100 | convention-android-application-firebase = { id = "convention.android.application.firebase", version = "unspecified" }
101 | convention-android-application-flavors = { id = "convention.android.application.flavors", version = "unspecified" }
102 | convention-android-application-jacoco = { id = "convention.android.application.jacoco", version = "unspecified" }
103 | convention-android-feature = { id = "convention.android.feature", version = "unspecified" }
104 | convention-android-hilt = { id = "convention.android.hilt", version = "unspecified" }
105 | convention-android-library = { id = "convention.android.library", version = "unspecified" }
106 | convention-android-library-compose = { id = "convention.android.library.compose", version = "unspecified" }
107 | convention-android-library-jacoco = { id = "convention.android.library.jacoco", version = "unspecified" }
108 | convention-android-lint = { id = "convention.android.lint", version = "unspecified" }
109 | convention-android-room = { id = "convention.android.room", version = "unspecified" }
110 | convention-android-test = { id = "convention.android.test", version = "unspecified" }
111 | convention-jvm-library = { id = "convention.jvm.library", version = "unspecified" }
112 | convention-kotlinter = { id = "convention.kotlinter", version = "unspecified" }
113 |
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aslansari/android-app-template/5b914d05aed2854247669bc806d9c15adb0feff2/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Sun Oct 10 21:53:17 EDT 2021
2 | distributionBase=GRADLE_USER_HOME
3 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.5-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 | includeBuild("build-logic")
3 | repositories {
4 | google()
5 | mavenCentral()
6 | gradlePluginPortal()
7 | maven(url = "https://plugins.gradle.org/m2/")
8 | }
9 | }
10 | dependencyResolutionManagement {
11 | repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
12 | repositories {
13 | google()
14 | mavenCentral()
15 | }
16 | }
17 | rootProject.name = "template"
18 | include(":app")
19 |
--------------------------------------------------------------------------------