├── library-compose
├── .gitignore
├── consumer-rules.pro
├── src
│ ├── main
│ │ ├── AndroidManifest.xml
│ │ └── java
│ │ │ └── com
│ │ │ └── ncorti
│ │ │ └── kotlin
│ │ │ └── template
│ │ │ └── app
│ │ │ ├── ComposeActivity.kt
│ │ │ └── ui
│ │ │ └── components
│ │ │ └── Factorial.kt
│ └── androidTest
│ │ └── java
│ │ └── com
│ │ └── ncorti
│ │ └── kotlin
│ │ └── template
│ │ └── app
│ │ └── FactorialTest.kt
├── proguard-rules.pro
└── build.gradle.kts
├── library-android
├── consumer-rules.pro
├── src
│ ├── main
│ │ └── java
│ │ │ └── com
│ │ │ └── ncorti
│ │ │ └── kotlin
│ │ │ └── template
│ │ │ └── library
│ │ │ └── android
│ │ │ └── ToastUtil.kt
│ └── androidTest
│ │ └── java
│ │ └── com
│ │ └── ncorti
│ │ └── kotlin
│ │ └── template
│ │ └── library
│ │ └── android
│ │ └── ToastUtilTest.kt
├── proguard-rules.pro
└── build.gradle.kts
├── .idea
├── icon.png
├── icon_dark.png
└── codeStyles
│ ├── codeStyleConfig.xml
│ └── Project.xml
├── renovate.json
├── gradle
├── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
└── libs.versions.toml
├── app
├── src
│ ├── main
│ │ ├── res
│ │ │ ├── mipmap-hdpi
│ │ │ │ ├── ic_launcher.png
│ │ │ │ └── ic_launcher_round.png
│ │ │ ├── mipmap-mdpi
│ │ │ │ ├── ic_launcher.png
│ │ │ │ └── ic_launcher_round.png
│ │ │ ├── mipmap-xhdpi
│ │ │ │ ├── ic_launcher.png
│ │ │ │ └── ic_launcher_round.png
│ │ │ ├── mipmap-xxhdpi
│ │ │ │ ├── ic_launcher.png
│ │ │ │ └── ic_launcher_round.png
│ │ │ ├── mipmap-xxxhdpi
│ │ │ │ ├── ic_launcher.png
│ │ │ │ └── ic_launcher_round.png
│ │ │ ├── values
│ │ │ │ ├── dimens.xml
│ │ │ │ ├── colors.xml
│ │ │ │ ├── styles.xml
│ │ │ │ └── strings.xml
│ │ │ ├── mipmap-anydpi-v26
│ │ │ │ ├── ic_launcher.xml
│ │ │ │ └── ic_launcher_round.xml
│ │ │ ├── drawable-v24
│ │ │ │ └── ic_launcher_foreground.xml
│ │ │ ├── layout
│ │ │ │ └── activity_main.xml
│ │ │ └── drawable
│ │ │ │ └── ic_launcher_background.xml
│ │ ├── AndroidManifest.xml
│ │ └── java
│ │ │ └── com
│ │ │ └── ncorti
│ │ │ └── kotlin
│ │ │ └── template
│ │ │ └── app
│ │ │ └── MainActivity.kt
│ └── androidTest
│ │ └── java
│ │ └── com
│ │ └── ncorti
│ │ └── kotlin
│ │ └── template
│ │ └── app
│ │ └── MainActivityTest.kt
├── proguard-rules.pro
└── build.gradle.kts
├── library-kotlin
├── build.gradle.kts
└── src
│ ├── main
│ └── java
│ │ └── com
│ │ └── ncorti
│ │ └── kotlin
│ │ └── template
│ │ └── library
│ │ └── FactorialCalculator.kt
│ └── test
│ └── java
│ └── com
│ └── ncorti
│ └── kotlin
│ └── template
│ └── library
│ └── FactorialCalculatorTest.kt
├── settings.gradle.kts
├── .github
├── ISSUE_TEMPLATE
│ ├── feature_request.md
│ └── bug_report.md
├── template-cleanup
│ └── README.md
├── workflows
│ ├── pre-merge.yaml
│ ├── cleanup.yaml
│ ├── publish-release.yaml
│ └── publish-snapshot.yaml
└── PULL_REQUEST_TEMPLATE
├── LICENSE
├── TROUBLESHOOTING.md
├── gradle.properties
├── gradlew.bat
├── .gitignore
├── README.md
├── gradlew
└── config
└── detekt
└── detekt.yml
/library-compose/.gitignore:
--------------------------------------------------------------------------------
1 | /build
--------------------------------------------------------------------------------
/library-android/consumer-rules.pro:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/library-compose/consumer-rules.pro:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/.idea/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dext7r/kotlin-android-template/main/.idea/icon.png
--------------------------------------------------------------------------------
/renovate.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": [
3 | "config:base",
4 | ":automergeMinor"
5 | ]
6 | }
7 |
--------------------------------------------------------------------------------
/.idea/icon_dark.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dext7r/kotlin-android-template/main/.idea/icon_dark.png
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dext7r/kotlin-android-template/main/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dext7r/kotlin-android-template/main/app/src/main/res/mipmap-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dext7r/kotlin-android-template/main/app/src/main/res/mipmap-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dext7r/kotlin-android-template/main/app/src/main/res/mipmap-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dext7r/kotlin-android-template/main/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dext7r/kotlin-android-template/main/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dext7r/kotlin-android-template/main/app/src/main/res/mipmap-hdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dext7r/kotlin-android-template/main/app/src/main/res/mipmap-mdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dext7r/kotlin-android-template/main/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dext7r/kotlin-android-template/main/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dext7r/kotlin-android-template/main/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/.idea/codeStyles/codeStyleConfig.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/app/src/main/res/values/dimens.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | 8dp
4 | 16dp
5 |
--------------------------------------------------------------------------------
/app/src/main/res/values/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #6200EE
4 | #3700B3
5 | #03DAC5
6 |
7 |
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | distributionBase=GRADLE_USER_HOME
2 | distributionPath=wrapper/dists
3 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.3-bin.zip
4 | networkTimeout=10000
5 | validateDistributionUrl=true
6 | zipStoreBase=GRADLE_USER_HOME
7 | zipStorePath=wrapper/dists
8 |
--------------------------------------------------------------------------------
/library-compose/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/library-android/src/main/java/com/ncorti/kotlin/template/library/android/ToastUtil.kt:
--------------------------------------------------------------------------------
1 | package com.ncorti.kotlin.template.library.android
2 |
3 | import android.content.Context
4 | import android.widget.Toast
5 |
6 | object ToastUtil {
7 |
8 | fun showToast(context: Context, message: String): Toast =
9 | Toast.makeText(context, message, Toast.LENGTH_SHORT).also {
10 | it.show()
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/library-kotlin/build.gradle.kts:
--------------------------------------------------------------------------------
1 | plugins {
2 | id("java-library")
3 | kotlin("jvm")
4 | id("maven-publish")
5 | publish
6 | }
7 |
8 | dependencies {
9 | testImplementation(libs.junit)
10 | }
11 |
12 | java {
13 | sourceCompatibility = JavaVersion.VERSION_17
14 | targetCompatibility = JavaVersion.VERSION_17
15 | withSourcesJar()
16 | withJavadocJar()
17 | }
18 |
19 | kotlin {
20 | jvmToolchain(17)
21 | }
22 |
--------------------------------------------------------------------------------
/app/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/settings.gradle.kts:
--------------------------------------------------------------------------------
1 | pluginManagement {
2 | repositories {
3 | gradlePluginPortal()
4 | google()
5 | mavenCentral()
6 | }
7 | }
8 |
9 | dependencyResolutionManagement {
10 | repositories {
11 | google()
12 | mavenCentral()
13 | }
14 | }
15 |
16 | rootProject.name = ("kotlin-android-template")
17 |
18 | enableFeaturePreview("TYPESAFE_PROJECT_ACCESSORS")
19 |
20 | include(
21 | "app",
22 | "library-android",
23 | "library-compose",
24 | "library-kotlin"
25 | )
26 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/feature_request.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Feature request
3 | about: Suggest an idea for this project
4 | ---
5 |
6 | ## ⚠️ Is your feature request related to a problem? Please describe
7 |
8 |
9 | ## 💡 Describe the solution you'd like
10 |
11 |
12 | ## 🤚 Do you want to develop this feature yourself?
13 |
14 | - [ ] Yes
15 | - [ ] No
16 |
--------------------------------------------------------------------------------
/library-kotlin/src/main/java/com/ncorti/kotlin/template/library/FactorialCalculator.kt:
--------------------------------------------------------------------------------
1 | package com.ncorti.kotlin.template.library
2 |
3 | object FactorialCalculator {
4 | private const val MAX_FACTORIAL_64BIT = 20
5 |
6 | tailrec fun computeFactorial(input: Long, temp: Long = 1L): Long =
7 | when {
8 | input < 0 -> error("Factorial is not defined for negative numbers")
9 | input > MAX_FACTORIAL_64BIT -> error("Only a factorial up to 20 can fit in a 64-bit Long")
10 | input == 0L -> temp
11 | else -> computeFactorial(input - 1, temp * input)
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/.github/template-cleanup/README.md:
--------------------------------------------------------------------------------
1 | # %NAME%
2 |
3 | 
4 |
5 | This is your new Kotlin Android Project! Happy hacking!
6 |
7 | ## Template ToDo list 👣
8 |
9 | - [x] Create a new template project.
10 | - [ ] Choose a [LICENSE](https://github.com/%REPOSITORY%/community/license/new?branch=main).
11 | - [ ] Set your `ORG_GRADLE_PROJECT_NEXUS_USERNAME`, `ORG_GRADLE_PROJECT_NEXUS_PASSWORD`, `ORG_GRADLE_PROJECT_SIGNING_KEY` and `ORG_GRADLE_PROJECT_SIGNING_PWD` secrets in [Settings](https://github.com/%REPOSITORY%/settings/secrets/actions).
12 | - [ ] Code some cool apps and libraries 🚀.
13 |
--------------------------------------------------------------------------------
/app/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | Kotlin Android Template
3 | This is just a template
4 | You can compute a factorial using the library-kotlin module. The result will be also shown in a notification using the library-android module.
5 | Insert a number to compute his factorial
6 | Compute
7 | The Result is: %s
8 | Please Enter a Number
9 | In Compose
10 |
11 |
--------------------------------------------------------------------------------
/.github/workflows/pre-merge.yaml:
--------------------------------------------------------------------------------
1 | name: Pre Merge Checks
2 |
3 | on:
4 | push:
5 | branches:
6 | - main
7 | pull_request:
8 | branches:
9 | - "*"
10 |
11 | jobs:
12 | gradle:
13 | runs-on: ubuntu-latest
14 | if: ${{ !contains(github.event.head_commit.message, 'ci skip') }}
15 | steps:
16 | - name: Checkout Repo
17 | uses: actions/checkout@v4
18 |
19 | - name: Setup Java
20 | uses: actions/setup-java@v4
21 | with:
22 | distribution: "zulu"
23 | java-version: "17"
24 |
25 | - name: Setup Gradle
26 | uses: gradle/actions/setup-gradle@v4
27 |
28 | - name: Run Gradle
29 | run: ./gradlew build publishToMavenLocal --continue
30 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/bug_report.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Bug report
3 | about: Create a report to help us improve
4 | ---
5 |
6 | ## 🐛 Describe the bug
7 |
8 |
9 | ## ⚠️ Current behavior
10 |
11 |
12 | ## ✅ Expected behavior
13 |
14 |
15 | ## 💣 Steps to reproduce
16 |
17 |
18 | ## 📷 Screenshots
19 |
20 |
21 | ## 📱 Tech info
22 | - Device:
23 | - OS:
24 | - Library/App version:
25 |
--------------------------------------------------------------------------------
/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
22 |
--------------------------------------------------------------------------------
/library-compose/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # You can control the set of applied configuration files using the
3 | # proguardFiles setting in build.gradle.
4 | #
5 | # For more details, see
6 | # http://developer.android.com/guide/developing/tools/proguard.html
7 |
8 | # If your project uses WebView with JS, uncomment the following
9 | # and specify the fully qualified class name to the JavaScript interface
10 | # class:
11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
12 | # public *;
13 | #}
14 |
15 | # Uncomment this to preserve the line number information for
16 | # debugging stack traces.
17 | #-keepattributes SourceFile,LineNumberTable
18 |
19 | # If you keep the line number information, uncomment this to
20 | # hide the original source file name.
21 | #-renamesourcefileattribute SourceFile
--------------------------------------------------------------------------------
/library-android/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # You can control the set of applied configuration files using the
3 | # proguardFiles setting in build.gradle.kts.kts
4 | #
5 | # For more details, see
6 | # http://developer.android.com/guide/developing/tools/proguard.html
7 |
8 | # If your project uses WebView with JS, uncomment the following
9 | # and specify the fully qualified class name to the JavaScript interface
10 | # class:
11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
12 | # public *;
13 | #}
14 |
15 | # Uncomment this to preserve the line number information for
16 | # debugging stack traces.
17 | #-keepattributes SourceFile,LineNumberTable
18 |
19 | # If you keep the line number information, uncomment this to
20 | # hide the original source file name.
21 | #-renamesourcefileattribute SourceFile
22 |
--------------------------------------------------------------------------------
/app/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
13 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
--------------------------------------------------------------------------------
/library-android/src/androidTest/java/com/ncorti/kotlin/template/library/android/ToastUtilTest.kt:
--------------------------------------------------------------------------------
1 | package com.ncorti.kotlin.template.library.android
2 |
3 | import android.widget.Toast
4 | import androidx.test.ext.junit.runners.AndroidJUnit4
5 | import androidx.test.platform.app.InstrumentationRegistry
6 | import org.junit.Assert.assertEquals
7 | import org.junit.Test
8 | import org.junit.runner.RunWith
9 |
10 | /**
11 | * Instrumented test, which will execute on an Android device.
12 | *
13 | * See [testing documentation](http://d.android.com/tools/testing).
14 | */
15 | @RunWith(AndroidJUnit4::class)
16 | class ToastUtilTest {
17 |
18 | @Test
19 | fun showCorrectToast() {
20 | val context = InstrumentationRegistry.getInstrumentation().targetContext
21 |
22 | val toast = ToastUtil.showToast(context, "test message")
23 |
24 | assertEquals(Toast.LENGTH_SHORT, toast.duration)
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright (c) 2020
2 |
3 | Permission is hereby granted, free of charge, to any person obtaining a copy
4 | of this software and associated documentation files (the "Software"), to deal
5 | in the Software without restriction, including without limitation the rights
6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7 | copies of the Software, and to permit persons to whom the Software is
8 | furnished to do so, subject to the following conditions:
9 |
10 | The above copyright notice and this permission notice shall be included in all
11 | copies or substantial portions of the Software.
12 |
13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
19 | SOFTWARE.
--------------------------------------------------------------------------------
/library-compose/src/androidTest/java/com/ncorti/kotlin/template/app/FactorialTest.kt:
--------------------------------------------------------------------------------
1 | package com.ncorti.kotlin.template.app
2 |
3 | import androidx.compose.ui.test.assert
4 | import androidx.compose.ui.test.assertIsDisplayed
5 | import androidx.compose.ui.test.hasText
6 | import androidx.compose.ui.test.junit4.createComposeRule
7 | import androidx.compose.ui.test.onNodeWithTag
8 | import androidx.compose.ui.test.onNodeWithText
9 | import androidx.compose.ui.test.performClick
10 | import androidx.compose.ui.test.performTextInput
11 | import com.ncorti.kotlin.template.app.ui.components.Factorial
12 | import org.junit.Rule
13 | import org.junit.Test
14 |
15 | class FactorialTest {
16 | @get:Rule
17 | val composeTestRule = createComposeRule()
18 |
19 | @Test
20 | fun useAppContext() {
21 | composeTestRule.setContent {
22 | Factorial()
23 | }
24 |
25 | composeTestRule.onNodeWithTag("Input").performClick().performTextInput("5")
26 | composeTestRule.onNodeWithText("COMPUTE").performClick()
27 | composeTestRule.onNodeWithTag("FactorialResult")
28 | .assertIsDisplayed()
29 | .assert(hasText("120"))
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/library-kotlin/src/test/java/com/ncorti/kotlin/template/library/FactorialCalculatorTest.kt:
--------------------------------------------------------------------------------
1 | package com.ncorti.kotlin.template.library
2 |
3 | import com.ncorti.kotlin.template.library.FactorialCalculator.computeFactorial
4 | import org.junit.Assert.assertEquals
5 | import org.junit.Assert.assertThrows
6 | import org.junit.Test
7 | import java.lang.Exception
8 | import java.lang.IllegalStateException
9 |
10 | /**
11 | * Example local unit test, which will execute on the development machine (host).
12 | *
13 | * See [testing documentation](http://d.android.com/tools/testing).
14 | */
15 | class FactorialCalculatorTest {
16 |
17 | @Test
18 | fun computeFactorial_withNegative_raiseException() {
19 | assertThrows(Exception::class.java) {
20 | computeFactorial(-1)
21 | }
22 | }
23 |
24 | @Test
25 | fun computeFactorial_forZero() {
26 | assertEquals(1, computeFactorial(0))
27 | }
28 |
29 | @Test
30 | fun computeFactorial_forFive() {
31 | assertEquals(120, computeFactorial(5))
32 | }
33 |
34 | @Test(expected = IllegalStateException::class)
35 | fun computeFactorial_TooLarge() {
36 | computeFactorial(21)
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/TROUBLESHOOTING.md:
--------------------------------------------------------------------------------
1 | # agp incompatibility with IntelliJ IDEA
2 |
3 | Opening this template in IDEA might result in an error like this:
4 |
5 | ```
6 | The project is using an incompatible version (AGP 8.0.0) of the Android Gradle plugin. Latest supported version is AGP 7.4.0
7 | ```
8 |
9 | This is caused by IDEA support for AGP lagging behind releases of AGP and is tracked by bugs like https://youtrack.jetbrains.com/issue/IDEA-317997.
10 |
11 | To get this working in the mean time, you can downgrade the version of `agp` in `libs.versions.toml` to a supported version.
12 |
13 | # Use correct JVM version
14 |
15 | This template requires you to have the correct JDK version in your path. Check which version you have by running `java --version` in a terminal. This should match the version specified in the Gradle build files e.g.:
16 |
17 | ```
18 | tasks.withType().configureEach {
19 | compilerOptions {
20 | jvmTarget.set(JvmTarget.JVM_17)
21 | }
22 | }
23 | ```
24 |
25 | If you use a JVM which is too new, you may see an error like this:
26 |
27 | ```
28 | FAILURE: Build failed with an exception.
29 | * What went wrong:
30 | Execution failed for task ':library-compose:detekt'.
31 | > Invalid value (20) passed to --jvm-target, must be one of [1.6, 1.8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18]
32 | ```
--------------------------------------------------------------------------------
/app/src/androidTest/java/com/ncorti/kotlin/template/app/MainActivityTest.kt:
--------------------------------------------------------------------------------
1 | package com.ncorti.kotlin.template.app
2 |
3 | import androidx.test.espresso.Espresso.onView
4 | import androidx.test.espresso.action.ViewActions.click
5 | import androidx.test.espresso.action.ViewActions.closeSoftKeyboard
6 | import androidx.test.espresso.action.ViewActions.typeText
7 | import androidx.test.espresso.assertion.ViewAssertions.matches
8 | import androidx.test.espresso.matcher.ViewMatchers.isDisplayed
9 | import androidx.test.espresso.matcher.ViewMatchers.withId
10 | import androidx.test.espresso.matcher.ViewMatchers.withText
11 | import androidx.test.ext.junit.rules.activityScenarioRule
12 | import androidx.test.ext.junit.runners.AndroidJUnit4
13 | import org.junit.Rule
14 | import org.junit.Test
15 | import org.junit.runner.RunWith
16 |
17 | @RunWith(AndroidJUnit4::class)
18 | class MainActivityTest {
19 |
20 | @get:Rule
21 | val rule = activityScenarioRule()
22 |
23 | @Test
24 | fun typeANumber_resultIsDisplayed() {
25 | onView(withId(R.id.edit_text_factorial)).perform(typeText("1"), closeSoftKeyboard())
26 | onView(withId(R.id.button_compute)).perform(click())
27 |
28 | onView(withId(R.id.text_result)).check(matches(isDisplayed()))
29 | onView(withId(R.id.text_result)).check(matches(withText("1")))
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/.github/PULL_REQUEST_TEMPLATE:
--------------------------------------------------------------------------------
1 |
2 |
3 | ## 🚀 Description
4 |
5 |
6 | ## 📄 Motivation and Context
7 |
8 |
9 |
10 | ## 🧪 How Has This Been Tested?
11 |
12 |
13 |
14 |
15 | ## 📷 Screenshots (if appropriate)
16 |
17 |
18 | ## 📦 Types of changes
19 |
20 | - [ ] Bug fix (non-breaking change which fixes an issue)
21 | - [ ] New feature (non-breaking change which adds functionality)
22 | - [ ] Breaking change (fix or feature that would cause existing functionality to not work as expected)
23 |
24 | ## ✅ Checklist
25 |
26 |
27 | - [ ] My code follows the code style of this project.
28 | - [ ] My change requires a change to the documentation.
29 | - [ ] I have updated the documentation accordingly.
--------------------------------------------------------------------------------
/.github/workflows/cleanup.yaml:
--------------------------------------------------------------------------------
1 | # GitHub Actions Workflow responsible for cleaning up the template repository from
2 | # the template-specific files and configurations. This workflow is supposed to be triggered automatically
3 | # when a new template-based repository has been created.
4 |
5 | name: Template Cleanup
6 | on:
7 | push:
8 | branches:
9 | - main
10 |
11 | jobs:
12 | template-cleanup:
13 | name: Template Cleanup
14 | runs-on: ubuntu-latest
15 | if: github.event.repository.name != 'kotlin-android-template'
16 | steps:
17 |
18 | # Check out current repository
19 | - name: Fetch Sources
20 | uses: actions/checkout@v4
21 | # Setup Java
22 | - uses: actions/setup-java@v4
23 | with:
24 | distribution: 'zulu'
25 | java-version: '17'
26 | # Setup Gradle
27 | - name: Setup Gradle
28 | uses: gradle/actions/setup-gradle@v4
29 | # Cleanup project
30 | - name: Cleanup
31 | run: ./gradlew templateCleanup
32 | # Commit modified files
33 | - name: Commit files
34 | run: |
35 | git config --local user.email "action@github.com"
36 | git config --local user.name "GitHub Action"
37 | git add .
38 | git commit -m "Template cleanup"
39 | # Push changes
40 | - name: Push changes
41 | uses: ad-m/github-push-action@v0.8.0
42 | with:
43 | branch: main
44 | github_token: ${{ secrets.GITHUB_TOKEN }}
45 |
--------------------------------------------------------------------------------
/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=-Xmx1536m
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
22 |
23 | # Edit these properties to configure publishing for your project
24 | GROUP=com.ncorti.kotlin.template
25 | VERSION=1.0.0
26 |
27 | # Edit these properties to configure the app version name and code
28 | APP_VERSION_NAME=1.0.0
29 | APP_VERSION_CODE=1
30 | APP_ID=com.ncorti.kotlin.template.app
--------------------------------------------------------------------------------
/.github/workflows/publish-release.yaml:
--------------------------------------------------------------------------------
1 | name: Publish Release
2 | on:
3 | push:
4 | tags:
5 | - '*'
6 | workflow_dispatch:
7 |
8 | jobs:
9 | publish:
10 | # remove this check when your secrets are setup and you're ready to publish
11 | if: ${{ github.repository == 'cortinico/kotlin-android-template'}}
12 | runs-on: [ubuntu-latest]
13 | env:
14 | GRADLE_OPTS: -Dorg.gradle.parallel=false
15 |
16 | steps:
17 |
18 | - name: Checkout Repo
19 | uses: actions/checkout@v4
20 |
21 | - name: Setup Java
22 | uses: actions/setup-java@v4
23 | with:
24 | distribution: 'zulu'
25 | java-version: '17'
26 |
27 | - name: Setup Gradle
28 | uses: gradle/actions/setup-gradle@v4
29 |
30 | - name: Publish to Maven Local
31 | run: ./gradlew publishToMavenLocal
32 | env:
33 | ORG_GRADLE_PROJECT_SIGNING_KEY: ${{ secrets.ORG_GRADLE_PROJECT_SIGNING_KEY }}
34 | ORG_GRADLE_PROJECT_SIGNING_PWD: ${{ secrets.ORG_GRADLE_PROJECT_SIGNING_PWD }}
35 |
36 | - name: Upload Build Artifacts
37 | uses: actions/upload-artifact@v4
38 | with:
39 | name: 'release-artifacts'
40 | path: '~/.m2/repository/'
41 |
42 | - name: Publish to the Snapshot Repository
43 | run: ./gradlew publishToSonatype closeAndReleaseStagingRepositories
44 | env:
45 | ORG_GRADLE_PROJECT_SIGNING_KEY: ${{ secrets.ORG_GRADLE_PROJECT_SIGNING_KEY }}
46 | ORG_GRADLE_PROJECT_SIGNING_PWD: ${{ secrets.ORG_GRADLE_PROJECT_SIGNING_PWD }}
47 | ORG_GRADLE_PROJECT_NEXUS_USERNAME: ${{ secrets.ORG_GRADLE_PROJECT_NEXUS_USERNAME }}
48 | ORG_GRADLE_PROJECT_NEXUS_PASSWORD: ${{ secrets.ORG_GRADLE_PROJECT_NEXUS_PASSWORD }}
--------------------------------------------------------------------------------
/app/src/main/java/com/ncorti/kotlin/template/app/MainActivity.kt:
--------------------------------------------------------------------------------
1 | package com.ncorti.kotlin.template.app
2 |
3 | import android.content.Intent
4 | import android.os.Bundle
5 | import android.view.View
6 | import androidx.appcompat.app.AppCompatActivity
7 | import com.ncorti.kotlin.template.app.databinding.ActivityMainBinding
8 | import com.ncorti.kotlin.template.library.FactorialCalculator
9 | import com.ncorti.kotlin.template.library.android.ToastUtil
10 |
11 | class MainActivity : AppCompatActivity() {
12 |
13 | private lateinit var binding: ActivityMainBinding
14 |
15 | override fun onCreate(savedInstanceState: Bundle?) {
16 | super.onCreate(savedInstanceState)
17 | binding = ActivityMainBinding.inflate(layoutInflater)
18 | setContentView(binding.root)
19 |
20 | binding.buttonCompute.setOnClickListener {
21 | val message = if (binding.editTextFactorial.text.isNotEmpty()) {
22 | val input = binding.editTextFactorial.text.toString().toLong()
23 | val result = try {
24 | FactorialCalculator.computeFactorial(input).toString()
25 | } catch (ex: IllegalStateException) {
26 | "Error: ${ex.message}"
27 | }
28 |
29 | binding.textResult.text = result
30 | binding.textResult.visibility = View.VISIBLE
31 | getString(R.string.notification_title, result)
32 | } else {
33 | getString(R.string.please_enter_a_number)
34 | }
35 | ToastUtil.showToast(this, message)
36 | }
37 |
38 | binding.buttonAppcompose.setOnClickListener {
39 | val intent = Intent(it.context, ComposeActivity::class.java)
40 | startActivity(intent)
41 | }
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/.github/workflows/publish-snapshot.yaml:
--------------------------------------------------------------------------------
1 | name: Publish Snapshot
2 | on:
3 | push:
4 | branches:
5 | - main
6 | workflow_dispatch:
7 |
8 | jobs:
9 | publish:
10 | # remove this check when your secrets are setup and you're ready to publish
11 | if: ${{ github.repository == 'cortinico/kotlin-android-template'}}
12 | runs-on: [ubuntu-latest]
13 | env:
14 | GRADLE_OPTS: -Dorg.gradle.parallel=false
15 |
16 | steps:
17 |
18 | - name: Checkout Repo
19 | uses: actions/checkout@v4
20 |
21 | - name: Setup Java
22 | uses: actions/setup-java@v4
23 | with:
24 | distribution: 'zulu'
25 | java-version: '17'
26 |
27 | - name: Setup Gradle
28 | uses: gradle/actions/setup-gradle@v4
29 |
30 | - name: Publish to Maven Local
31 | run: ./gradlew publishToMavenLocal
32 | env:
33 | ORG_GRADLE_PROJECT_SIGNING_KEY: ${{ secrets.ORG_GRADLE_PROJECT_SIGNING_KEY }}
34 | ORG_GRADLE_PROJECT_SIGNING_PWD: ${{ secrets.ORG_GRADLE_PROJECT_SIGNING_PWD }}
35 | ORG_GRADLE_PROJECT_USE_SNAPSHOT: true
36 |
37 | - name: Upload Build Artifacts
38 | uses: actions/upload-artifact@v4
39 | with:
40 | name: 'snapshot-artifacts'
41 | path: '~/.m2/repository/'
42 |
43 | - name: Publish to the Snapshot Repository
44 | run: ./gradlew publishToSonatype
45 | env:
46 | ORG_GRADLE_PROJECT_SIGNING_KEY: ${{ secrets.ORG_GRADLE_PROJECT_SIGNING_KEY }}
47 | ORG_GRADLE_PROJECT_SIGNING_PWD: ${{ secrets.ORG_GRADLE_PROJECT_SIGNING_PWD }}
48 | ORG_GRADLE_PROJECT_NEXUS_USERNAME: ${{ secrets.ORG_GRADLE_PROJECT_NEXUS_USERNAME }}
49 | ORG_GRADLE_PROJECT_NEXUS_PASSWORD: ${{ secrets.ORG_GRADLE_PROJECT_NEXUS_PASSWORD }}
50 | ORG_GRADLE_PROJECT_USE_SNAPSHOT: true
--------------------------------------------------------------------------------
/app/src/main/res/drawable-v24/ic_launcher_foreground.xml:
--------------------------------------------------------------------------------
1 |
7 |
8 |
9 |
15 |
18 |
21 |
22 |
23 |
24 |
30 |
--------------------------------------------------------------------------------
/library-android/build.gradle.kts:
--------------------------------------------------------------------------------
1 | import org.gradle.kotlin.dsl.withType
2 | import org.jetbrains.kotlin.gradle.dsl.JvmTarget
3 | import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
4 |
5 | plugins {
6 | id("com.android.library")
7 | kotlin("android")
8 | id("maven-publish")
9 | publish
10 | }
11 |
12 | android {
13 | compileSdk = libs.versions.compile.sdk.version.get().toInt()
14 |
15 | defaultConfig {
16 | minSdk = libs.versions.min.sdk.version.get().toInt()
17 | namespace = "com.ncorti.kotlin.template.library.android"
18 |
19 | testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
20 | consumerProguardFiles("consumer-rules.pro")
21 | }
22 |
23 | compileOptions {
24 | sourceCompatibility = JavaVersion.VERSION_17
25 | targetCompatibility = JavaVersion.VERSION_17
26 | }
27 |
28 | buildTypes {
29 | getByName("release") {
30 | isMinifyEnabled = false
31 | proguardFiles(
32 | getDefaultProguardFile("proguard-android-optimize.txt"),
33 | "proguard-rules.pro"
34 | )
35 | }
36 | }
37 |
38 | publishing {
39 | singleVariant("release") {
40 | withSourcesJar()
41 | withJavadocJar()
42 | }
43 | }
44 |
45 | lint {
46 | warningsAsErrors = true
47 | abortOnError = true
48 | disable.add("GradleDependency")
49 | }
50 | }
51 |
52 | tasks.withType().configureEach {
53 | compilerOptions {
54 | jvmTarget.set(JvmTarget.JVM_17)
55 | }
56 | }
57 |
58 | dependencies {
59 | implementation(libs.androidx.appcompat)
60 | implementation(libs.androidx.core.ktx)
61 |
62 | testImplementation(libs.junit)
63 |
64 | androidTestImplementation(libs.androidx.test.runner)
65 | androidTestImplementation(libs.androidx.test.ext.junit)
66 | }
67 |
--------------------------------------------------------------------------------
/library-compose/build.gradle.kts:
--------------------------------------------------------------------------------
1 | import org.gradle.kotlin.dsl.withType
2 | import org.jetbrains.kotlin.gradle.dsl.JvmTarget
3 | import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
4 |
5 | plugins {
6 | id("com.android.library")
7 | kotlin("android")
8 | alias(libs.plugins.compose.compiler)
9 |
10 | }
11 |
12 | android {
13 | compileSdk = libs.versions.compile.sdk.version.get().toInt()
14 |
15 | defaultConfig {
16 | minSdk = libs.versions.min.sdk.version.get().toInt()
17 | namespace = "com.ncorti.kotlin.template.library.compose"
18 |
19 | testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
20 |
21 | vectorDrawables {
22 | useSupportLibrary = true
23 | }
24 | }
25 |
26 | compileOptions {
27 | sourceCompatibility = JavaVersion.VERSION_17
28 | targetCompatibility = JavaVersion.VERSION_17
29 | }
30 |
31 | buildFeatures {
32 | compose = true
33 | viewBinding = true
34 | buildConfig = false
35 | }
36 |
37 | lint {
38 | warningsAsErrors = true
39 | abortOnError = true
40 | disable.add("GradleDependency")
41 | }
42 | }
43 |
44 | tasks.withType().configureEach {
45 | compilerOptions {
46 | jvmTarget.set(JvmTarget.JVM_17)
47 | }
48 | }
49 |
50 | dependencies {
51 | implementation(projects.libraryKotlin)
52 | implementation(libs.androidx.appcompat)
53 | implementation(libs.androidx.core.ktx)
54 |
55 | implementation(platform(libs.compose.bom))
56 | implementation(libs.androidx.activity.compose)
57 | implementation(libs.compose.ui)
58 | implementation(libs.compose.ui.tooling)
59 | implementation(libs.compose.foundation)
60 | implementation(libs.compose.material)
61 |
62 | testImplementation(libs.junit)
63 |
64 | debugImplementation(libs.compose.ui.test.manifest)
65 | androidTestImplementation(libs.compose.ui.test.junit4)
66 | androidTestImplementation(libs.androidx.test.runner)
67 | androidTestImplementation(libs.androidx.test.ext.junit)
68 | }
69 |
--------------------------------------------------------------------------------
/library-compose/src/main/java/com/ncorti/kotlin/template/app/ComposeActivity.kt:
--------------------------------------------------------------------------------
1 | package com.ncorti.kotlin.template.app
2 |
3 | import android.os.Bundle
4 | import androidx.activity.ComponentActivity
5 | import androidx.activity.compose.setContent
6 | import androidx.compose.animation.ExperimentalAnimationApi
7 | import androidx.compose.foundation.layout.Box
8 | import androidx.compose.foundation.layout.fillMaxSize
9 | import androidx.compose.foundation.layout.padding
10 | import androidx.compose.foundation.layout.wrapContentSize
11 | import androidx.compose.material.MaterialTheme
12 | import androidx.compose.material.Scaffold
13 | import androidx.compose.material.Text
14 | import androidx.compose.material.TopAppBar
15 | import androidx.compose.runtime.Composable
16 | import androidx.compose.ui.Alignment
17 | import androidx.compose.ui.Modifier
18 | import androidx.compose.ui.tooling.preview.Preview
19 | import androidx.compose.ui.unit.dp
20 | import com.ncorti.kotlin.template.app.ui.components.Factorial
21 |
22 | class ComposeActivity : ComponentActivity() {
23 | @ExperimentalAnimationApi
24 | override fun onCreate(savedInstanceState: Bundle?) {
25 | super.onCreate(savedInstanceState)
26 | setContent {
27 | AppMain()
28 | }
29 | }
30 | }
31 |
32 | @ExperimentalAnimationApi
33 | @Preview
34 | @Composable
35 | fun AppMain() {
36 | MaterialTheme {
37 | Scaffold(
38 | topBar = {
39 | TopAppBar(
40 | title = { Text(text = "Kotlin Android Template") },
41 | backgroundColor = MaterialTheme.colors.primary
42 | )
43 | },
44 | backgroundColor = MaterialTheme.colors.background
45 | ) {
46 | Box(
47 | modifier = Modifier
48 | .padding(it)
49 | .fillMaxSize()
50 | .wrapContentSize(align = Alignment.Center)
51 | .padding(horizontal = 8.dp)
52 | ) {
53 | Factorial()
54 | }
55 | }
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/gradle/libs.versions.toml:
--------------------------------------------------------------------------------
1 | [versions]
2 | agp = "8.13.0"
3 | androidx_activity_compose = "1.11.0"
4 | androidx_test = "1.7.0"
5 | androidx_test_ext = "1.3.0"
6 | appcompat = "1.7.1"
7 | compile_sdk_version = "36"
8 | compose-bom = "2025.09.00"
9 | compose_compilerextension = "1.5.5"
10 | constraint_layout = "2.2.1"
11 | core_ktx = "1.17.0"
12 | detekt = "1.23.8"
13 | espresso_core = "3.7.0"
14 | junit = "4.13.2"
15 | kotlin = "2.2.20"
16 | min_sdk_version = "23"
17 | nexus_publish = "2.0.0"
18 | target_sdk_version = "36"
19 |
20 | [libraries]
21 | junit = { module = "junit:junit", version.ref = "junit" }
22 | androidx_activity_compose = { module = "androidx.activity:activity-compose", version.ref = "androidx_activity_compose" }
23 | androidx_appcompat = { module = "androidx.appcompat:appcompat", version.ref = "appcompat" }
24 | androidx_constraint_layout = { module = "androidx.constraintlayout:constraintlayout", version.ref = "constraint_layout" }
25 | androidx_core_ktx = { module = "androidx.core:core-ktx", version.ref = "core_ktx" }
26 | androidx_test_rules = { module = "androidx.test:rules", version.ref = "androidx_test" }
27 | androidx_test_runner = { module = "androidx.test:runner", version.ref = "androidx_test" }
28 | androidx_test_ext_junit = { module = "androidx.test.ext:junit", version.ref = "androidx_test_ext" }
29 | androidx_test_ext_junit_ktx = { module = "androidx.test.ext:junit-ktx", version.ref = "androidx_test_ext" }
30 | compose-bom = { module = "androidx.compose:compose-bom", version.ref = "compose-bom" }
31 | compose_material = { module = "androidx.compose.material:material" }
32 | compose_foundation = { module = "androidx.compose.foundation:foundation" }
33 | compose_ui = { module = "androidx.compose.ui:ui" }
34 | compose_ui_tooling = { module = "androidx.compose.ui:ui-tooling" }
35 | compose_ui_test_junit4 = { module = "androidx.compose.ui:ui-test-junit4" }
36 | compose_ui_test_manifest = { module = "androidx.compose.ui:ui-test-manifest" }
37 | detekt_formatting = { module = "io.gitlab.arturbosch.detekt:detekt-formatting", version.ref = "detekt" }
38 | espresso_core = { module = "androidx.test.espresso:espresso-core", version.ref = "espresso_core" }
39 | agp = { module = "com.android.tools.build:gradle", version.ref = "agp" }
40 | kgp = { module = "org.jetbrains.kotlin:kotlin-gradle-plugin", version.ref = "kotlin" }
41 |
42 | [plugins]
43 | detekt = { id = "io.gitlab.arturbosch.detekt", version.ref = "detekt" }
44 | compose-compiler = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" }
45 | nexus-publish = { id = "io.github.gradle-nexus.publish-plugin", version.ref = "nexus_publish" }
--------------------------------------------------------------------------------
/app/build.gradle.kts:
--------------------------------------------------------------------------------
1 | import org.jetbrains.kotlin.gradle.dsl.JvmTarget
2 | import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
3 |
4 | plugins {
5 | id("com.android.application")
6 | kotlin("android")
7 | }
8 |
9 | val APP_VERSION_NAME : String by project
10 | val APP_VERSION_CODE : String by project
11 | val APP_ID : String by project
12 |
13 | android {
14 | compileSdk = libs.versions.compile.sdk.version.get().toInt()
15 |
16 | defaultConfig {
17 | minSdk = libs.versions.min.sdk.version.get().toInt()
18 | namespace = APP_ID
19 |
20 | applicationId = APP_ID
21 | versionCode = APP_VERSION_CODE.toInt()
22 | versionName = APP_VERSION_NAME
23 | testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
24 | }
25 | buildFeatures {
26 | viewBinding = true
27 | }
28 | compileOptions {
29 | sourceCompatibility = JavaVersion.VERSION_17
30 | targetCompatibility = JavaVersion.VERSION_17
31 | }
32 | buildTypes {
33 | getByName("release") {
34 | isMinifyEnabled = false
35 | proguardFiles(
36 | getDefaultProguardFile("proguard-android-optimize.txt"),
37 | "proguard-rules.pro"
38 | )
39 | }
40 | }
41 |
42 | lint {
43 | warningsAsErrors = true
44 | abortOnError = true
45 | disable.add("GradleDependency")
46 | }
47 |
48 | // Use this block to configure different flavors
49 | // flavorDimensions("version")
50 | // productFlavors {
51 | // create("full") {
52 | // dimension = "version"
53 | // applicationIdSuffix = ".full"
54 | // }
55 | // create("demo") {
56 | // dimension = "version"
57 | // applicationIdSuffix = ".demo"
58 | // }
59 | // }
60 | }
61 |
62 | tasks.withType().configureEach {
63 | compilerOptions {
64 | jvmTarget.set(JvmTarget.JVM_17)
65 | }
66 | }
67 |
68 | dependencies {
69 | implementation(projects.libraryAndroid)
70 | implementation(projects.libraryCompose)
71 | implementation(projects.libraryKotlin)
72 |
73 | implementation(libs.androidx.appcompat)
74 | implementation(libs.androidx.constraint.layout)
75 | implementation(libs.androidx.core.ktx)
76 |
77 | testImplementation(libs.junit)
78 |
79 | androidTestImplementation(libs.androidx.test.ext.junit)
80 | androidTestImplementation(libs.androidx.test.ext.junit.ktx)
81 | androidTestImplementation(libs.androidx.test.rules)
82 | androidTestImplementation(libs.espresso.core)
83 | }
84 |
--------------------------------------------------------------------------------
/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 | @rem SPDX-License-Identifier: Apache-2.0
17 | @rem
18 |
19 | @if "%DEBUG%"=="" @echo off
20 | @rem ##########################################################################
21 | @rem
22 | @rem Gradle startup script for Windows
23 | @rem
24 | @rem ##########################################################################
25 |
26 | @rem Set local scope for the variables with windows NT shell
27 | if "%OS%"=="Windows_NT" setlocal
28 |
29 | set DIRNAME=%~dp0
30 | if "%DIRNAME%"=="" set DIRNAME=.
31 | @rem This is normally unused
32 | set APP_BASE_NAME=%~n0
33 | set APP_HOME=%DIRNAME%
34 |
35 | @rem Resolve any "." and ".." in APP_HOME to make it shorter.
36 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
37 |
38 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
39 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
40 |
41 | @rem Find java.exe
42 | if defined JAVA_HOME goto findJavaFromJavaHome
43 |
44 | set JAVA_EXE=java.exe
45 | %JAVA_EXE% -version >NUL 2>&1
46 | if %ERRORLEVEL% equ 0 goto execute
47 |
48 | echo. 1>&2
49 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2
50 | echo. 1>&2
51 | echo Please set the JAVA_HOME variable in your environment to match the 1>&2
52 | echo location of your Java installation. 1>&2
53 |
54 | goto fail
55 |
56 | :findJavaFromJavaHome
57 | set JAVA_HOME=%JAVA_HOME:"=%
58 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
59 |
60 | if exist "%JAVA_EXE%" goto execute
61 |
62 | echo. 1>&2
63 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2
64 | echo. 1>&2
65 | echo Please set the JAVA_HOME variable in your environment to match the 1>&2
66 | echo location of your Java installation. 1>&2
67 |
68 | goto fail
69 |
70 | :execute
71 | @rem Setup the command line
72 |
73 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
74 |
75 |
76 | @rem Execute Gradle
77 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
78 |
79 | :end
80 | @rem End local scope for the variables with windows NT shell
81 | if %ERRORLEVEL% equ 0 goto mainEnd
82 |
83 | :fail
84 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
85 | rem the _cmd.exe /c_ return code!
86 | set EXIT_CODE=%ERRORLEVEL%
87 | if %EXIT_CODE% equ 0 set EXIT_CODE=1
88 | if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
89 | exit /b %EXIT_CODE%
90 |
91 | :mainEnd
92 | if "%OS%"=="Windows_NT" endlocal
93 |
94 | :omega
95 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_main.xml:
--------------------------------------------------------------------------------
1 |
2 |
10 |
11 |
19 |
20 |
29 |
30 |
42 |
43 |
54 |
55 |
64 |
65 |
72 |
73 |
--------------------------------------------------------------------------------
/library-compose/src/main/java/com/ncorti/kotlin/template/app/ui/components/Factorial.kt:
--------------------------------------------------------------------------------
1 | package com.ncorti.kotlin.template.app.ui.components
2 |
3 | import androidx.compose.animation.AnimatedVisibility
4 | import androidx.compose.animation.ExperimentalAnimationApi
5 | import androidx.compose.foundation.layout.Arrangement
6 | import androidx.compose.foundation.layout.Column
7 | import androidx.compose.foundation.layout.fillMaxWidth
8 | import androidx.compose.foundation.layout.padding
9 | import androidx.compose.foundation.text.KeyboardOptions
10 | import androidx.compose.material.Button
11 | import androidx.compose.material.MaterialTheme
12 | import androidx.compose.material.Text
13 | import androidx.compose.material.TextField
14 | import androidx.compose.runtime.Composable
15 | import androidx.compose.runtime.getValue
16 | import androidx.compose.runtime.mutableStateOf
17 | import androidx.compose.runtime.remember
18 | import androidx.compose.runtime.rememberCoroutineScope
19 | import androidx.compose.runtime.setValue
20 | import androidx.compose.ui.Alignment
21 | import androidx.compose.ui.Modifier
22 | import androidx.compose.ui.platform.testTag
23 | import androidx.compose.ui.text.input.KeyboardType
24 | import androidx.compose.ui.tooling.preview.Preview
25 | import androidx.compose.ui.unit.dp
26 | import androidx.compose.ui.unit.sp
27 | import com.ncorti.kotlin.template.library.FactorialCalculator
28 | import kotlinx.coroutines.launch
29 | import java.lang.IllegalStateException
30 |
31 | @Suppress("LongMethod")
32 | @Composable
33 | fun Factorial(modifier: Modifier = Modifier) {
34 | var textFieldState by remember { mutableStateOf("") }
35 | var factorialResult by remember { mutableStateOf(null) }
36 | var showFactorialError by remember { mutableStateOf(false) }
37 | val scope = rememberCoroutineScope()
38 | Column(
39 | modifier = modifier,
40 | verticalArrangement = Arrangement.spacedBy(8.dp)
41 | ) {
42 | Text(
43 | text = "This is just a template",
44 | style = MaterialTheme.typography.h6
45 | )
46 | Text(
47 | text = "You can compute a factorial using the library-kotlin module.",
48 | style = MaterialTheme.typography.body1
49 | )
50 | TextField(
51 | value = textFieldState,
52 | label = { Text(text = "Insert a number to compute the factorial") },
53 | onValueChange = {
54 | textFieldState = it
55 | },
56 | modifier = Modifier
57 | .fillMaxWidth()
58 | .testTag("Input"),
59 | keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number)
60 | )
61 | if (showFactorialError) {
62 | Text(
63 | text = "Valid range is 0-20",
64 | style = MaterialTheme.typography.caption,
65 | modifier = Modifier.testTag("ErrorMsg")
66 | )
67 | }
68 | AnimatedVisibility(
69 | visible = factorialResult != null,
70 | modifier = Modifier
71 | .padding(top = 8.dp)
72 | .align(alignment = Alignment.End)
73 | ) {
74 | Text(
75 | text = "$factorialResult",
76 | style = MaterialTheme.typography.subtitle1.copy(fontSize = 24.sp),
77 | modifier = Modifier.testTag("FactorialResult")
78 | )
79 | }
80 | Button(
81 | modifier = Modifier
82 | .padding(top = 8.dp)
83 | .align(alignment = Alignment.End),
84 | onClick = {
85 | scope.launch {
86 | @Suppress("SwallowedException")
87 | try {
88 | factorialResult =
89 | FactorialCalculator.computeFactorial(textFieldState.toLong())
90 | showFactorialError = false
91 | } catch (ex: IllegalStateException) {
92 | showFactorialError = true
93 | }
94 | }
95 | },
96 | content = { Text(text = "COMPUTE") }
97 | )
98 | }
99 | }
100 |
101 | @ExperimentalAnimationApi
102 | @Preview
103 | @Composable
104 | fun FactorialPreview() {
105 | MaterialTheme {
106 | Factorial()
107 | }
108 | }
109 |
--------------------------------------------------------------------------------
/.idea/codeStyles/Project.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
10 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 | xmlns:android
34 |
35 | ^$
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 | xmlns:.*
45 |
46 | ^$
47 |
48 |
49 | BY_NAME
50 |
51 |
52 |
53 |
54 |
55 |
56 | .*:id
57 |
58 | http://schemas.android.com/apk/res/android
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 | .*:name
68 |
69 | http://schemas.android.com/apk/res/android
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 | name
79 |
80 | ^$
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 | style
90 |
91 | ^$
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 | .*
101 |
102 | ^$
103 |
104 |
105 | BY_NAME
106 |
107 |
108 |
109 |
110 |
111 |
112 | .*
113 |
114 | http://schemas.android.com/apk/res/android
115 |
116 |
117 | ANDROID_ATTRIBUTE_ORDER
118 |
119 |
120 |
121 |
122 |
123 |
124 | .*
125 |
126 | .*
127 |
128 |
129 | BY_NAME
130 |
131 |
132 |
133 |
134 |
135 |
136 |
137 |
138 |
139 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Created by https://www.toptal.com/developers/gitignore/api/macos,linux,gradle,windows,android,androidstudio,java,kotlin
2 | # Edit at https://www.toptal.com/developers/gitignore?templates=macos,linux,gradle,windows,android,androidstudio,java,kotlin
3 |
4 | ### Android ###
5 | # Gradle files
6 | .gradle/
7 | build/
8 |
9 | # Local configuration file (sdk path, etc)
10 | local.properties
11 |
12 | # Log/OS Files
13 | *.log
14 |
15 | # Android Studio generated files and folders
16 | captures/
17 | .externalNativeBuild/
18 | .cxx/
19 | *.apk
20 | output.json
21 |
22 | # IntelliJ
23 | *.iml
24 | .idea/
25 | misc.xml
26 | deploymentTargetDropDown.xml
27 | render.experimental.xml
28 |
29 | # Keystore files
30 | *.jks
31 | *.keystore
32 |
33 | # Google Services (e.g. APIs or Firebase)
34 | google-services.json
35 |
36 | # Android Profiling
37 | *.hprof
38 |
39 | ### Android Patch ###
40 | gen-external-apklibs
41 |
42 | # Replacement of .externalNativeBuild directories introduced
43 | # with Android Studio 3.5.
44 |
45 | ### Java ###
46 | # Compiled class file
47 | *.class
48 |
49 | # Log file
50 |
51 | # BlueJ files
52 | *.ctxt
53 |
54 | # Mobile Tools for Java (J2ME)
55 | .mtj.tmp/
56 |
57 | # Package Files #
58 | *.jar
59 | *.war
60 | *.nar
61 | *.ear
62 | *.zip
63 | *.tar.gz
64 | *.rar
65 |
66 | # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
67 | hs_err_pid*
68 | replay_pid*
69 |
70 | ### Kotlin ###
71 |
72 | .kotlin/
73 |
74 | # Compiled class file
75 |
76 | # Log file
77 |
78 | # BlueJ files
79 |
80 | # Mobile Tools for Java (J2ME)
81 |
82 | # Package Files #
83 |
84 | # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
85 |
86 | ### Linux ###
87 | *~
88 |
89 | # temporary files which can be created if a process still has a handle open of a deleted file
90 | .fuse_hidden*
91 |
92 | # KDE directory preferences
93 | .directory
94 |
95 | # Linux trash folder which might appear on any partition or disk
96 | .Trash-*
97 |
98 | # .nfs files are created when an open file is removed but is still being accessed
99 | .nfs*
100 |
101 | ### macOS ###
102 | # General
103 | .DS_Store
104 | .AppleDouble
105 | .LSOverride
106 |
107 | # Icon must end with two \r
108 | Icon
109 |
110 |
111 | # Thumbnails
112 | ._*
113 |
114 | # Files that might appear in the root of a volume
115 | .DocumentRevisions-V100
116 | .fseventsd
117 | .Spotlight-V100
118 | .TemporaryItems
119 | .Trashes
120 | .VolumeIcon.icns
121 | .com.apple.timemachine.donotpresent
122 |
123 | # Directories potentially created on remote AFP share
124 | .AppleDB
125 | .AppleDesktop
126 | Network Trash Folder
127 | Temporary Items
128 | .apdisk
129 |
130 | ### macOS Patch ###
131 | # iCloud generated files
132 | *.icloud
133 |
134 | ### Windows ###
135 | # Windows thumbnail cache files
136 | Thumbs.db
137 | Thumbs.db:encryptable
138 | ehthumbs.db
139 | ehthumbs_vista.db
140 |
141 | # Dump file
142 | *.stackdump
143 |
144 | # Folder config file
145 | [Dd]esktop.ini
146 |
147 | # Recycle Bin used on file shares
148 | $RECYCLE.BIN/
149 |
150 | # Windows Installer files
151 | *.cab
152 | *.msi
153 | *.msix
154 | *.msm
155 | *.msp
156 |
157 | # Windows shortcuts
158 | *.lnk
159 |
160 | ### Gradle ###
161 | .gradle
162 | **/build/
163 | !src/**/build/
164 |
165 | # Ignore Gradle GUI config
166 | gradle-app.setting
167 |
168 | # Avoid ignoring Gradle wrapper jar file (.jar files are usually ignored)
169 | !gradle-wrapper.jar
170 |
171 | # Avoid ignore Gradle wrappper properties
172 | !gradle-wrapper.properties
173 |
174 | # Cache of project
175 | .gradletasknamecache
176 |
177 | # Eclipse Gradle plugin generated files
178 | # Eclipse Core
179 | .project
180 | # JDT-specific (Eclipse Java Development Tools)
181 | .classpath
182 |
183 | ### Gradle Patch ###
184 | # Java heap dump
185 |
186 | ### AndroidStudio ###
187 | # Covers files to be ignored for android development using Android Studio.
188 |
189 | # Built application files
190 | *.ap_
191 | *.aab
192 |
193 | # Files for the ART/Dalvik VM
194 | *.dex
195 |
196 | # Java class files
197 |
198 | # Generated files
199 | bin/
200 | gen/
201 | out/
202 |
203 | # Gradle files
204 |
205 | # Signing files
206 | .signing/
207 |
208 | # Local configuration file (sdk path, etc)
209 |
210 | # Proguard folder generated by Eclipse
211 | proguard/
212 |
213 | # Log Files
214 |
215 | # Android Studio
216 | /*/build/
217 | /*/local.properties
218 | /*/out
219 | /*/*/build
220 | /*/*/production
221 | .navigation/
222 | *.ipr
223 | *.swp
224 |
225 | # Keystore files
226 |
227 | # Google Services (e.g. APIs or Firebase)
228 | # google-services.json
229 |
230 | # Android Patch
231 |
232 | # External native build folder generated in Android Studio 2.2 and later
233 | .externalNativeBuild
234 |
235 | # NDK
236 | obj/
237 |
238 | # IntelliJ IDEA
239 | *.iws
240 | /out/
241 |
242 | # User-specific configurations
243 | .idea/caches/
244 | .idea/libraries/
245 | .idea/shelf/
246 | .idea/workspace.xml
247 | .idea/tasks.xml
248 | .idea/.name
249 | .idea/compiler.xml
250 | .idea/copyright/profiles_settings.xml
251 | .idea/encodings.xml
252 | .idea/misc.xml
253 | .idea/modules.xml
254 | .idea/scopes/scope_settings.xml
255 | .idea/dictionaries
256 | .idea/vcs.xml
257 | .idea/jsLibraryMappings.xml
258 | .idea/datasources.xml
259 | .idea/dataSources.ids
260 | .idea/sqlDataSources.xml
261 | .idea/dynamic.xml
262 | .idea/uiDesigner.xml
263 | .idea/assetWizardSettings.xml
264 | .idea/gradle.xml
265 | .idea/jarRepositories.xml
266 | .idea/navEditor.xml
267 |
268 | # Legacy Eclipse project files
269 | .cproject
270 | .settings/
271 |
272 | # Mobile Tools for Java (J2ME)
273 |
274 | # Package Files #
275 |
276 | # virtual machine crash logs (Reference: http://www.java.com/en/download/help/error_hotspot.xml)
277 |
278 | ## Plugin-specific files:
279 |
280 | # mpeltonen/sbt-idea plugin
281 | .idea_modules/
282 |
283 | # JIRA plugin
284 | atlassian-ide-plugin.xml
285 |
286 | # Mongo Explorer plugin
287 | .idea/mongoSettings.xml
288 |
289 | # Crashlytics plugin (for Android Studio and IntelliJ)
290 | com_crashlytics_export_strings.xml
291 | crashlytics.properties
292 | crashlytics-build.properties
293 | fabric.properties
294 |
295 | ### AndroidStudio Patch ###
296 |
297 | !/gradle/wrapper/gradle-wrapper.jar
298 |
299 | # End of https://www.toptal.com/developers/gitignore/api/macos,linux,gradle,windows,android,androidstudio,java,kotlin
300 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # kotlin-android-template 🤖
2 |
3 | [](https://github.com/cortinico/kotlin-android-template/generate)   
4 |
5 | A simple Github template that lets you create an **Android/Kotlin** project and be up and running in a **few seconds**.
6 |
7 | This template is focused on delivering a project with **static analysis** and **continuous integration** already in place.
8 |
9 | ## How to use 👣
10 |
11 | Just click on [](https://github.com/cortinico/kotlin-android-template/generate) button to create a new repo starting from this template.
12 |
13 | Once created don't forget to update the:
14 | - [App ID](buildSrc/src/main/java/Coordinates.kt)
15 | - AndroidManifest ([here](app/src/main/AndroidManifest.xml) and [here](library-compose/src/main/AndroidManifest.xml))
16 | - Package of the source files
17 |
18 | ## Features 🎨
19 |
20 | - **100% Kotlin-only template**.
21 | - 4 Sample modules (Android app, Android library, Kotlin library, Jetpack Compose Activity).
22 | - Jetpack Compose setup ready to use.
23 | - Sample Espresso, Instrumentation & JUnit tests.
24 | - 100% Gradle Kotlin DSL setup.
25 | - CI Setup with GitHub Actions.
26 | - Publish to **Maven Central** with Github Actions.
27 | - Dependency versions managed via `buildSrc`.
28 | - Kotlin Static Analysis via `detekt` and `ktlint`.
29 | - Issues Template (bug report + feature request).
30 | - Pull Request Template.
31 |
32 | ## Troubleshooting
33 |
34 | For help with issues which you might encounter when using this template, please refer to [TROUBLESHOOTING.md](TROUBLESHOOTING.md)
35 |
36 | ## Gradle Setup 🐘
37 |
38 | This template is using [**Gradle Kotlin DSL**](https://docs.gradle.org/current/userguide/kotlin_dsl.html) as well as the [Plugin DSL](https://docs.gradle.org/current/userguide/plugins.html#sec:plugins_block) to setup the build.
39 |
40 | Dependencies are centralized inside the Gradle Version Catalog in the [libs.versions.toml](gradle/libs.versions.toml) file in the `gradle` folder.
41 |
42 | ## Static Analysis 🔍
43 |
44 | This template is using [**detekt**](https://github.com/detekt/detekt) to analyze the source code, with the configuration that is stored in the [detekt.yml](config/detekt/detekt.yml) file (the file has been generated with the `detektGenerateConfig` task). It also uses the **detekt-formatting** plugin which includes the ktlint rules (see https://detekt.dev/docs/rules/formatting/).
45 |
46 | ## CI ⚙️
47 |
48 | This template is using [**GitHub Actions**](https://github.com/cortinico/kotlin-android-template/actions) as CI. You don't need to setup any external service and you should have a running CI once you start using this template, just make sure that you turn on the "Read and Write permissions" on the Action Settings of your repository.
49 |
50 | There are currently the following workflows available:
51 | - [Validate Gradle Wrapper](.github/workflows/gradle-wrapper-validation.yml) - Will check that the gradle wrapper has a valid checksum
52 | - [Pre Merge Checks](.github/workflows/pre-merge.yaml) - Will run the `build`, `check` and `publishToMavenLocal` tasks.
53 | - [Publish Snapshot](.github/workflows/publish-snapshot.yaml) - Will publish a `-SNAPSHOT` of the libraries to Sonatype.
54 | - [Publish Release](.github/workflows/publish-release.yaml) - Will publish a new release version of the libraries to Maven Central on tag pushes.
55 |
56 | ## Publishing 🚀
57 |
58 | The template is setup to be **ready to publish** a library/artifact on a Maven Repository.
59 |
60 | For every module you want to publish you simply have to add the `publish` plugin:
61 |
62 | ```
63 | plugins {
64 | publish
65 | }
66 | ```
67 |
68 | ### To Maven Central
69 |
70 | In order to use this template to publish on Maven Central, you need to configure some secrets on your repository:
71 |
72 | | Secret name | Value |
73 | | --- | --- |
74 | | `ORG_GRADLE_PROJECT_NEXUS_USERNAME` | The username you use to access Sonatype's services (such as [Nexus](https://oss.sonatype.org/) and [Jira](https://issues.sonatype.org/)) |
75 | | `ORG_GRADLE_PROJECT_NEXUS_PASSWORD` | The password you use to access Sonatype's services (such as [Nexus](https://oss.sonatype.org/) and [Jira](https://issues.sonatype.org/)) |
76 | | `ORG_GRADLE_PROJECT_SIGNING_KEY` | The GPG Private key to sign your artifacts. You can obtain it with `gpg --armor --export-secret-keys ` or you can create one key online on [pgpkeygen.com](https://pgpkeygen.com). The key starts with a `-----BEGIN PGP PRIVATE KEY BLOCK-----`. |
77 | | `ORG_GRADLE_PROJECT_SIGNING_PWD` | The passphrase to unlock your private key (you picked it when creating the key). |
78 |
79 | The template already attaches `-sources.jar` to your publications via the new AGP publishing DSL.
80 |
81 | Once set up, the following workflows will take care of publishing:
82 |
83 | - [Publish Snapshot](.github/workflows/publish-snapshot.yaml) - To publish `-SNAPSHOT` versions to Sonatype. The workflow is setup to run either manually (with `workflow_dispatch`) or on every merge.
84 | - [Publish Release](.github/workflows/publish-release.yaml) - Will publish a new release version of the libraries to Maven Central on tag pushes. You can trigger the workflow also manually if needed.
85 |
86 | ### To Jitpack
87 |
88 | If you're using [JitPack](https://jitpack.io/), you don't need any further configuration and you can just configure the repo on JitPack.
89 |
90 | You probably want to disable the [Publish Snapshot] and [Publish Release](.github/workflows/publish-release.yaml) workflows (delete the files), as Jitpack will take care of that for you.
91 |
92 | ## Project Structure
93 |
94 | The project includes three sub-projects, each in their own subdirectories:
95 |
96 | - **`app`:** The source for the final Android application.
97 | - **`library-android`:** The source for an Android library including UI.
98 | - **`library-kotlin`:** The source for a UI-less Kotlin library.
99 | - **`library-compose`:** The source for a UI library with Jetpack Compose library.
100 |
101 | The following additional top-level directories configure & support building the app & projects:
102 |
103 | - **`buildSrc`:** Contains shared Gradle logic as [precompiled script plugins](https://docs.gradle.org/current/userguide/custom_plugins.html#sec:precompiled_plugins)
104 | - **`config`:** Contains the [Detekt configuration file](https://detekt.dev/docs/introduction/configurations/).
105 | - **`gradle`:** Contains Gradle Configuration files such as the Gradle Version Catalog and the [Gradle Wrapper](https://docs.gradle.org/current/userguide/gradle_wrapper.html).
106 |
107 | Finally, the following hidden top-level directories provide functionality for specific development systems:
108 |
109 | - **`.github`:** Defines the [Github Actions](https://github.com/features/actions) CI tasks and templates for new pull requests, issues, etc.
110 | - **`.idea`:** Sets common initial project settings when the project is opened in [Android Studio](https://developer.android.com/studio) or [IntelliJ IDEA](https://www.jetbrains.com/idea/).
111 |
112 | ## Contributing 🤝
113 |
114 | Feel free to open a issue or submit a pull request for any bugs/improvements.
115 |
--------------------------------------------------------------------------------
/gradlew:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 |
3 | #
4 | # Copyright © 2015-2021 the original 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 | # SPDX-License-Identifier: Apache-2.0
19 | #
20 |
21 | ##############################################################################
22 | #
23 | # Gradle start up script for POSIX generated by Gradle.
24 | #
25 | # Important for running:
26 | #
27 | # (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
28 | # noncompliant, but you have some other compliant shell such as ksh or
29 | # bash, then to run this script, type that shell name before the whole
30 | # command line, like:
31 | #
32 | # ksh Gradle
33 | #
34 | # Busybox and similar reduced shells will NOT work, because this script
35 | # requires all of these POSIX shell features:
36 | # * functions;
37 | # * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
38 | # «${var#prefix}», «${var%suffix}», and «$( cmd )»;
39 | # * compound commands having a testable exit status, especially «case»;
40 | # * various built-in commands including «command», «set», and «ulimit».
41 | #
42 | # Important for patching:
43 | #
44 | # (2) This script targets any POSIX shell, so it avoids extensions provided
45 | # by Bash, Ksh, etc; in particular arrays are avoided.
46 | #
47 | # The "traditional" practice of packing multiple parameters into a
48 | # space-separated string is a well documented source of bugs and security
49 | # problems, so this is (mostly) avoided, by progressively accumulating
50 | # options in "$@", and eventually passing that to Java.
51 | #
52 | # Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
53 | # and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
54 | # see the in-line comments for details.
55 | #
56 | # There are tweaks for specific operating systems such as AIX, CygWin,
57 | # Darwin, MinGW, and NonStop.
58 | #
59 | # (3) This script is generated from the Groovy template
60 | # https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
61 | # within the Gradle project.
62 | #
63 | # You can find Gradle at https://github.com/gradle/gradle/.
64 | #
65 | ##############################################################################
66 |
67 | # Attempt to set APP_HOME
68 |
69 | # Resolve links: $0 may be a link
70 | app_path=$0
71 |
72 | # Need this for daisy-chained symlinks.
73 | while
74 | APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path
75 | [ -h "$app_path" ]
76 | do
77 | ls=$( ls -ld "$app_path" )
78 | link=${ls#*' -> '}
79 | case $link in #(
80 | /*) app_path=$link ;; #(
81 | *) app_path=$APP_HOME$link ;;
82 | esac
83 | done
84 |
85 | # This is normally unused
86 | # shellcheck disable=SC2034
87 | APP_BASE_NAME=${0##*/}
88 | # Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
89 | APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s
90 | ' "$PWD" ) || exit
91 |
92 | # Use the maximum available, or set MAX_FD != -1 to use that value.
93 | MAX_FD=maximum
94 |
95 | warn () {
96 | echo "$*"
97 | } >&2
98 |
99 | die () {
100 | echo
101 | echo "$*"
102 | echo
103 | exit 1
104 | } >&2
105 |
106 | # OS specific support (must be 'true' or 'false').
107 | cygwin=false
108 | msys=false
109 | darwin=false
110 | nonstop=false
111 | case "$( uname )" in #(
112 | CYGWIN* ) cygwin=true ;; #(
113 | Darwin* ) darwin=true ;; #(
114 | MSYS* | MINGW* ) msys=true ;; #(
115 | NONSTOP* ) nonstop=true ;;
116 | esac
117 |
118 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
119 |
120 |
121 | # Determine the Java command to use to start the JVM.
122 | if [ -n "$JAVA_HOME" ] ; then
123 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
124 | # IBM's JDK on AIX uses strange locations for the executables
125 | JAVACMD=$JAVA_HOME/jre/sh/java
126 | else
127 | JAVACMD=$JAVA_HOME/bin/java
128 | fi
129 | if [ ! -x "$JAVACMD" ] ; then
130 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
131 |
132 | Please set the JAVA_HOME variable in your environment to match the
133 | location of your Java installation."
134 | fi
135 | else
136 | JAVACMD=java
137 | if ! command -v java >/dev/null 2>&1
138 | then
139 | die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
140 |
141 | Please set the JAVA_HOME variable in your environment to match the
142 | location of your Java installation."
143 | fi
144 | fi
145 |
146 | # Increase the maximum file descriptors if we can.
147 | if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
148 | case $MAX_FD in #(
149 | max*)
150 | # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.
151 | # shellcheck disable=SC2039,SC3045
152 | MAX_FD=$( ulimit -H -n ) ||
153 | warn "Could not query maximum file descriptor limit"
154 | esac
155 | case $MAX_FD in #(
156 | '' | soft) :;; #(
157 | *)
158 | # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
159 | # shellcheck disable=SC2039,SC3045
160 | ulimit -n "$MAX_FD" ||
161 | warn "Could not set maximum file descriptor limit to $MAX_FD"
162 | esac
163 | fi
164 |
165 | # Collect all arguments for the java command, stacking in reverse order:
166 | # * args from the command line
167 | # * the main class name
168 | # * -classpath
169 | # * -D...appname settings
170 | # * --module-path (only if needed)
171 | # * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
172 |
173 | # For Cygwin or MSYS, switch paths to Windows format before running java
174 | if "$cygwin" || "$msys" ; then
175 | APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
176 | CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" )
177 |
178 | JAVACMD=$( cygpath --unix "$JAVACMD" )
179 |
180 | # Now convert the arguments - kludge to limit ourselves to /bin/sh
181 | for arg do
182 | if
183 | case $arg in #(
184 | -*) false ;; # don't mess with options #(
185 | /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath
186 | [ -e "$t" ] ;; #(
187 | *) false ;;
188 | esac
189 | then
190 | arg=$( cygpath --path --ignore --mixed "$arg" )
191 | fi
192 | # Roll the args list around exactly as many times as the number of
193 | # args, so each arg winds up back in the position where it started, but
194 | # possibly modified.
195 | #
196 | # NB: a `for` loop captures its iteration list before it begins, so
197 | # changing the positional parameters here affects neither the number of
198 | # iterations, nor the values presented in `arg`.
199 | shift # remove old arg
200 | set -- "$@" "$arg" # push replacement arg
201 | done
202 | fi
203 |
204 |
205 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
206 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
207 |
208 | # Collect all arguments for the java command:
209 | # * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,
210 | # and any embedded shellness will be escaped.
211 | # * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be
212 | # treated as '${Hostname}' itself on the command line.
213 |
214 | set -- \
215 | "-Dorg.gradle.appname=$APP_BASE_NAME" \
216 | -classpath "$CLASSPATH" \
217 | org.gradle.wrapper.GradleWrapperMain \
218 | "$@"
219 |
220 | # Stop when "xargs" is not available.
221 | if ! command -v xargs >/dev/null 2>&1
222 | then
223 | die "xargs is not available"
224 | fi
225 |
226 | # Use "xargs" to parse quoted args.
227 | #
228 | # With -n1 it outputs one arg per line, with the quotes and backslashes removed.
229 | #
230 | # In Bash we could simply go:
231 | #
232 | # readarray ARGS < <( xargs -n1 <<<"$var" ) &&
233 | # set -- "${ARGS[@]}" "$@"
234 | #
235 | # but POSIX shell has neither arrays nor command substitution, so instead we
236 | # post-process each arg (as a line of input to sed) to backslash-escape any
237 | # character that might be a shell metacharacter, then use eval to reverse
238 | # that process (while maintaining the separation between arguments), and wrap
239 | # the whole thing up as a single "set" statement.
240 | #
241 | # This will of course break if any of these variables contains a newline or
242 | # an unmatched quote.
243 | #
244 |
245 | eval "set -- $(
246 | printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
247 | xargs -n1 |
248 | sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
249 | tr '\n' ' '
250 | )" '"$@"'
251 |
252 | exec "$JAVACMD" "$@"
253 |
--------------------------------------------------------------------------------
/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 | checkExhaustiveness: false
14 | # when writing own rules with new properties, exclude the property path e.g.: 'my_rule_set,.*>.*>[my_property]'
15 | excludes: ""
16 |
17 | processors:
18 | active: true
19 | exclude:
20 | - "DetektProgressListener"
21 | # - 'KtFileCountProcessor'
22 | # - 'PackageCountProcessor'
23 | # - 'ClassCountProcessor'
24 | # - 'FunctionCountProcessor'
25 | # - 'PropertyCountProcessor'
26 | # - 'ProjectComplexityProcessor'
27 | # - 'ProjectCognitiveComplexityProcessor'
28 | # - 'ProjectLLOCProcessor'
29 | # - 'ProjectCLOCProcessor'
30 | # - 'ProjectLOCProcessor'
31 | # - 'ProjectSLOCProcessor'
32 | # - 'LicenseHeaderLoaderExtension'
33 |
34 | console-reports:
35 | active: true
36 | exclude:
37 | - "ProjectStatisticsReport"
38 | - "ComplexityReport"
39 | - "NotificationReport"
40 | - "FindingsReport"
41 | - "FileBasedFindingsReport"
42 | # - 'LiteFindingsReport'
43 |
44 | output-reports:
45 | active: true
46 | exclude:
47 | # - 'TxtOutputReport'
48 | # - 'XmlOutputReport'
49 | # - 'HtmlOutputReport'
50 | # - 'MdOutputReport'
51 | # - 'SarifOutputReport'
52 |
53 | comments:
54 | active: true
55 | AbsentOrWrongFileLicense:
56 | active: false
57 | licenseTemplateFile: "license.template"
58 | licenseTemplateIsRegex: false
59 | CommentOverPrivateFunction:
60 | active: false
61 | CommentOverPrivateProperty:
62 | active: false
63 | DeprecatedBlockTag:
64 | active: false
65 | EndOfSentenceFormat:
66 | active: false
67 | endOfSentenceFormat: '([.?!][ \t\n\r\f<])|([.?!:]$)'
68 | KDocReferencesNonPublicProperty:
69 | active: false
70 | excludes:
71 | [
72 | "**/test/**",
73 | "**/androidTest/**",
74 | "**/commonTest/**",
75 | "**/jvmTest/**",
76 | "**/androidUnitTest/**",
77 | "**/androidInstrumentedTest/**",
78 | "**/jsTest/**",
79 | "**/iosTest/**",
80 | ]
81 | OutdatedDocumentation:
82 | active: false
83 | matchTypeParameters: true
84 | matchDeclarationsOrder: true
85 | allowParamOnConstructorProperties: false
86 | UndocumentedPublicClass:
87 | active: false
88 | excludes:
89 | [
90 | "**/test/**",
91 | "**/androidTest/**",
92 | "**/commonTest/**",
93 | "**/jvmTest/**",
94 | "**/androidUnitTest/**",
95 | "**/androidInstrumentedTest/**",
96 | "**/jsTest/**",
97 | "**/iosTest/**",
98 | ]
99 | searchInNestedClass: true
100 | searchInInnerClass: true
101 | searchInInnerObject: true
102 | searchInInnerInterface: true
103 | searchInProtectedClass: false
104 | UndocumentedPublicFunction:
105 | active: false
106 | excludes:
107 | [
108 | "**/test/**",
109 | "**/androidTest/**",
110 | "**/commonTest/**",
111 | "**/jvmTest/**",
112 | "**/androidUnitTest/**",
113 | "**/androidInstrumentedTest/**",
114 | "**/jsTest/**",
115 | "**/iosTest/**",
116 | ]
117 | searchProtectedFunction: false
118 | UndocumentedPublicProperty:
119 | active: false
120 | excludes:
121 | [
122 | "**/test/**",
123 | "**/androidTest/**",
124 | "**/commonTest/**",
125 | "**/jvmTest/**",
126 | "**/androidUnitTest/**",
127 | "**/androidInstrumentedTest/**",
128 | "**/jsTest/**",
129 | "**/iosTest/**",
130 | ]
131 | searchProtectedProperty: false
132 |
133 | complexity:
134 | active: true
135 | CognitiveComplexMethod:
136 | active: false
137 | threshold: 15
138 | ComplexCondition:
139 | active: true
140 | threshold: 4
141 | ComplexInterface:
142 | active: false
143 | threshold: 10
144 | includeStaticDeclarations: false
145 | includePrivateDeclarations: false
146 | ignoreOverloaded: false
147 | CyclomaticComplexMethod:
148 | active: true
149 | threshold: 15
150 | ignoreSingleWhenExpression: false
151 | ignoreSimpleWhenEntries: false
152 | ignoreNestingFunctions: false
153 | nestingFunctions:
154 | - "also"
155 | - "apply"
156 | - "forEach"
157 | - "isNotNull"
158 | - "ifNull"
159 | - "let"
160 | - "run"
161 | - "use"
162 | - "with"
163 | LabeledExpression:
164 | active: false
165 | ignoredLabels: []
166 | LargeClass:
167 | active: true
168 | threshold: 600
169 | LongMethod:
170 | active: true
171 | threshold: 60
172 | LongParameterList:
173 | active: true
174 | functionThreshold: 6
175 | constructorThreshold: 7
176 | ignoreDefaultParameters: false
177 | ignoreDataClasses: true
178 | ignoreAnnotatedParameter: []
179 | MethodOverloading:
180 | active: false
181 | threshold: 6
182 | NamedArguments:
183 | active: false
184 | threshold: 3
185 | ignoreArgumentsMatchingNames: false
186 | NestedBlockDepth:
187 | active: true
188 | threshold: 4
189 | NestedScopeFunctions:
190 | active: false
191 | threshold: 1
192 | functions:
193 | - "kotlin.apply"
194 | - "kotlin.run"
195 | - "kotlin.with"
196 | - "kotlin.let"
197 | - "kotlin.also"
198 | ReplaceSafeCallChainWithRun:
199 | active: false
200 | StringLiteralDuplication:
201 | active: false
202 | excludes:
203 | [
204 | "**/test/**",
205 | "**/androidTest/**",
206 | "**/commonTest/**",
207 | "**/jvmTest/**",
208 | "**/androidUnitTest/**",
209 | "**/androidInstrumentedTest/**",
210 | "**/jsTest/**",
211 | "**/iosTest/**",
212 | ]
213 | threshold: 3
214 | ignoreAnnotation: true
215 | excludeStringsWithLessThan5Characters: true
216 | ignoreStringsRegex: "$^"
217 | TooManyFunctions:
218 | active: true
219 | excludes:
220 | [
221 | "**/test/**",
222 | "**/androidTest/**",
223 | "**/commonTest/**",
224 | "**/jvmTest/**",
225 | "**/androidUnitTest/**",
226 | "**/androidInstrumentedTest/**",
227 | "**/jsTest/**",
228 | "**/iosTest/**",
229 | ]
230 | thresholdInFiles: 11
231 | thresholdInClasses: 11
232 | thresholdInInterfaces: 11
233 | thresholdInObjects: 11
234 | thresholdInEnums: 11
235 | ignoreDeprecated: false
236 | ignorePrivate: false
237 | ignoreOverridden: false
238 |
239 | coroutines:
240 | active: true
241 | GlobalCoroutineUsage:
242 | active: false
243 | InjectDispatcher:
244 | active: true
245 | dispatcherNames:
246 | - "IO"
247 | - "Default"
248 | - "Unconfined"
249 | RedundantSuspendModifier:
250 | active: true
251 | SleepInsteadOfDelay:
252 | active: true
253 | SuspendFunSwallowedCancellation:
254 | active: false
255 | SuspendFunWithCoroutineScopeReceiver:
256 | active: false
257 | SuspendFunWithFlowReturnType:
258 | active: true
259 |
260 | empty-blocks:
261 | active: true
262 | EmptyCatchBlock:
263 | active: true
264 | allowedExceptionNameRegex: "_|(ignore|expected).*"
265 | EmptyClassBlock:
266 | active: true
267 | EmptyDefaultConstructor:
268 | active: true
269 | EmptyDoWhileBlock:
270 | active: true
271 | EmptyElseBlock:
272 | active: true
273 | EmptyFinallyBlock:
274 | active: true
275 | EmptyForBlock:
276 | active: true
277 | EmptyFunctionBlock:
278 | active: true
279 | ignoreOverridden: false
280 | EmptyIfBlock:
281 | active: true
282 | EmptyInitBlock:
283 | active: true
284 | EmptyKtFile:
285 | active: true
286 | EmptySecondaryConstructor:
287 | active: true
288 | EmptyTryBlock:
289 | active: true
290 | EmptyWhenBlock:
291 | active: true
292 | EmptyWhileBlock:
293 | active: true
294 |
295 | exceptions:
296 | active: true
297 | ExceptionRaisedInUnexpectedLocation:
298 | active: true
299 | methodNames:
300 | - "equals"
301 | - "finalize"
302 | - "hashCode"
303 | - "toString"
304 | InstanceOfCheckForException:
305 | active: true
306 | excludes:
307 | [
308 | "**/test/**",
309 | "**/androidTest/**",
310 | "**/commonTest/**",
311 | "**/jvmTest/**",
312 | "**/androidUnitTest/**",
313 | "**/androidInstrumentedTest/**",
314 | "**/jsTest/**",
315 | "**/iosTest/**",
316 | ]
317 | NotImplementedDeclaration:
318 | active: false
319 | ObjectExtendsThrowable:
320 | active: false
321 | PrintStackTrace:
322 | active: true
323 | RethrowCaughtException:
324 | active: true
325 | ReturnFromFinally:
326 | active: true
327 | ignoreLabeled: false
328 | SwallowedException:
329 | active: true
330 | ignoredExceptionTypes:
331 | - "InterruptedException"
332 | - "MalformedURLException"
333 | - "NumberFormatException"
334 | - "ParseException"
335 | allowedExceptionNameRegex: "_|(ignore|expected).*"
336 | ThrowingExceptionFromFinally:
337 | active: true
338 | ThrowingExceptionInMain:
339 | active: false
340 | ThrowingExceptionsWithoutMessageOrCause:
341 | active: true
342 | excludes:
343 | [
344 | "**/test/**",
345 | "**/androidTest/**",
346 | "**/commonTest/**",
347 | "**/jvmTest/**",
348 | "**/androidUnitTest/**",
349 | "**/androidInstrumentedTest/**",
350 | "**/jsTest/**",
351 | "**/iosTest/**",
352 | ]
353 | exceptions:
354 | - "ArrayIndexOutOfBoundsException"
355 | - "Exception"
356 | - "IllegalArgumentException"
357 | - "IllegalMonitorStateException"
358 | - "IllegalStateException"
359 | - "IndexOutOfBoundsException"
360 | - "NullPointerException"
361 | - "RuntimeException"
362 | - "Throwable"
363 | ThrowingNewInstanceOfSameException:
364 | active: true
365 | TooGenericExceptionCaught:
366 | active: true
367 | excludes:
368 | [
369 | "**/test/**",
370 | "**/androidTest/**",
371 | "**/commonTest/**",
372 | "**/jvmTest/**",
373 | "**/androidUnitTest/**",
374 | "**/androidInstrumentedTest/**",
375 | "**/jsTest/**",
376 | "**/iosTest/**",
377 | ]
378 | exceptionNames:
379 | - "ArrayIndexOutOfBoundsException"
380 | - "Error"
381 | - "Exception"
382 | - "IllegalMonitorStateException"
383 | - "IndexOutOfBoundsException"
384 | - "NullPointerException"
385 | - "RuntimeException"
386 | - "Throwable"
387 | allowedExceptionNameRegex: "_|(ignore|expected).*"
388 | TooGenericExceptionThrown:
389 | active: true
390 | exceptionNames:
391 | - "Error"
392 | - "Exception"
393 | - "RuntimeException"
394 | - "Throwable"
395 |
396 | naming:
397 | active: true
398 | BooleanPropertyNaming:
399 | active: false
400 | allowedPattern: "^(is|has|are)"
401 | ClassNaming:
402 | active: true
403 | classPattern: "[A-Z][a-zA-Z0-9]*"
404 | ConstructorParameterNaming:
405 | active: true
406 | parameterPattern: "[a-z][A-Za-z0-9]*"
407 | privateParameterPattern: "[a-z][A-Za-z0-9]*"
408 | excludeClassPattern: "$^"
409 | EnumNaming:
410 | active: true
411 | enumEntryPattern: "[A-Z][_a-zA-Z0-9]*"
412 | ForbiddenClassName:
413 | active: false
414 | forbiddenName: []
415 | FunctionMaxLength:
416 | active: false
417 | maximumFunctionNameLength: 30
418 | FunctionMinLength:
419 | active: false
420 | minimumFunctionNameLength: 3
421 | FunctionNaming:
422 | active: true
423 | excludes:
424 | [
425 | "**/test/**",
426 | "**/androidTest/**",
427 | "**/commonTest/**",
428 | "**/jvmTest/**",
429 | "**/androidUnitTest/**",
430 | "**/androidInstrumentedTest/**",
431 | "**/jsTest/**",
432 | "**/iosTest/**",
433 | ]
434 | functionPattern: "[a-z][a-zA-Z0-9]*"
435 | excludeClassPattern: "$^"
436 | ignoreAnnotated: "Composable"
437 | FunctionParameterNaming:
438 | active: true
439 | parameterPattern: "[a-z][A-Za-z0-9]*"
440 | excludeClassPattern: "$^"
441 | InvalidPackageDeclaration:
442 | active: true
443 | rootPackage: ""
444 | requireRootInDeclaration: false
445 | LambdaParameterNaming:
446 | active: false
447 | parameterPattern: "[a-z][A-Za-z0-9]*|_"
448 | MatchingDeclarationName:
449 | active: true
450 | mustBeFirst: true
451 | MemberNameEqualsClassName:
452 | active: true
453 | ignoreOverridden: true
454 | NoNameShadowing:
455 | active: true
456 | NonBooleanPropertyPrefixedWithIs:
457 | active: false
458 | ObjectPropertyNaming:
459 | active: true
460 | constantPattern: "[A-Za-z][_A-Za-z0-9]*"
461 | propertyPattern: "[A-Za-z][_A-Za-z0-9]*"
462 | privatePropertyPattern: "(_)?[A-Za-z][_A-Za-z0-9]*"
463 | PackageNaming:
464 | active: true
465 | packagePattern: '[a-z]+(\.[a-z][A-Za-z0-9]*)*'
466 | TopLevelPropertyNaming:
467 | active: true
468 | constantPattern: "[A-Z][_A-Z0-9]*"
469 | propertyPattern: "[A-Za-z][_A-Za-z0-9]*"
470 | privatePropertyPattern: "_?[A-Za-z][_A-Za-z0-9]*"
471 | VariableMaxLength:
472 | active: false
473 | maximumVariableNameLength: 64
474 | VariableMinLength:
475 | active: false
476 | minimumVariableNameLength: 1
477 | VariableNaming:
478 | active: true
479 | variablePattern: "[a-z][A-Za-z0-9]*"
480 | privateVariablePattern: "(_)?[a-z][A-Za-z0-9]*"
481 | excludeClassPattern: "$^"
482 |
483 | performance:
484 | active: true
485 | ArrayPrimitive:
486 | active: true
487 | CouldBeSequence:
488 | active: false
489 | threshold: 3
490 | ForEachOnRange:
491 | active: true
492 | excludes:
493 | [
494 | "**/test/**",
495 | "**/androidTest/**",
496 | "**/commonTest/**",
497 | "**/jvmTest/**",
498 | "**/androidUnitTest/**",
499 | "**/androidInstrumentedTest/**",
500 | "**/jsTest/**",
501 | "**/iosTest/**",
502 | ]
503 | SpreadOperator:
504 | active: true
505 | excludes:
506 | [
507 | "**/test/**",
508 | "**/androidTest/**",
509 | "**/commonTest/**",
510 | "**/jvmTest/**",
511 | "**/androidUnitTest/**",
512 | "**/androidInstrumentedTest/**",
513 | "**/jsTest/**",
514 | "**/iosTest/**",
515 | ]
516 | UnnecessaryPartOfBinaryExpression:
517 | active: false
518 | UnnecessaryTemporaryInstantiation:
519 | active: true
520 |
521 | potential-bugs:
522 | active: true
523 | AvoidReferentialEquality:
524 | active: true
525 | forbiddenTypePatterns:
526 | - "kotlin.String"
527 | CastNullableToNonNullableType:
528 | active: false
529 | CastToNullableType:
530 | active: false
531 | Deprecation:
532 | active: false
533 | DontDowncastCollectionTypes:
534 | active: false
535 | DoubleMutabilityForCollection:
536 | active: true
537 | mutableTypes:
538 | - "kotlin.collections.MutableList"
539 | - "kotlin.collections.MutableMap"
540 | - "kotlin.collections.MutableSet"
541 | - "java.util.ArrayList"
542 | - "java.util.LinkedHashSet"
543 | - "java.util.HashSet"
544 | - "java.util.LinkedHashMap"
545 | - "java.util.HashMap"
546 | ElseCaseInsteadOfExhaustiveWhen:
547 | active: false
548 | ignoredSubjectTypes: []
549 | EqualsAlwaysReturnsTrueOrFalse:
550 | active: true
551 | EqualsWithHashCodeExist:
552 | active: true
553 | ExitOutsideMain:
554 | active: false
555 | ExplicitGarbageCollectionCall:
556 | active: true
557 | HasPlatformType:
558 | active: true
559 | IgnoredReturnValue:
560 | active: true
561 | restrictToConfig: true
562 | returnValueAnnotations:
563 | - "CheckResult"
564 | - "*.CheckResult"
565 | - "CheckReturnValue"
566 | - "*.CheckReturnValue"
567 | ignoreReturnValueAnnotations:
568 | - "CanIgnoreReturnValue"
569 | - "*.CanIgnoreReturnValue"
570 | returnValueTypes:
571 | - "kotlin.sequences.Sequence"
572 | - "kotlinx.coroutines.flow.*Flow"
573 | - "java.util.stream.*Stream"
574 | ignoreFunctionCall: []
575 | ImplicitDefaultLocale:
576 | active: true
577 | ImplicitUnitReturnType:
578 | active: false
579 | allowExplicitReturnType: true
580 | InvalidRange:
581 | active: true
582 | IteratorHasNextCallsNextMethod:
583 | active: true
584 | IteratorNotThrowingNoSuchElementException:
585 | active: true
586 | LateinitUsage:
587 | active: false
588 | excludes:
589 | [
590 | "**/test/**",
591 | "**/androidTest/**",
592 | "**/commonTest/**",
593 | "**/jvmTest/**",
594 | "**/androidUnitTest/**",
595 | "**/androidInstrumentedTest/**",
596 | "**/jsTest/**",
597 | "**/iosTest/**",
598 | ]
599 | ignoreOnClassesPattern: ""
600 | MapGetWithNotNullAssertionOperator:
601 | active: true
602 | MissingPackageDeclaration:
603 | active: false
604 | excludes: ["**/*.kts"]
605 | NullCheckOnMutableProperty:
606 | active: false
607 | NullableToStringCall:
608 | active: false
609 | PropertyUsedBeforeDeclaration:
610 | active: false
611 | UnconditionalJumpStatementInLoop:
612 | active: false
613 | UnnecessaryNotNullCheck:
614 | active: false
615 | UnnecessaryNotNullOperator:
616 | active: true
617 | UnnecessarySafeCall:
618 | active: true
619 | UnreachableCatchBlock:
620 | active: true
621 | UnreachableCode:
622 | active: true
623 | UnsafeCallOnNullableType:
624 | active: true
625 | excludes:
626 | [
627 | "**/test/**",
628 | "**/androidTest/**",
629 | "**/commonTest/**",
630 | "**/jvmTest/**",
631 | "**/androidUnitTest/**",
632 | "**/androidInstrumentedTest/**",
633 | "**/jsTest/**",
634 | "**/iosTest/**",
635 | ]
636 | UnsafeCast:
637 | active: true
638 | UnusedUnaryOperator:
639 | active: true
640 | UselessPostfixExpression:
641 | active: true
642 | WrongEqualsTypeParameter:
643 | active: true
644 |
645 | style:
646 | active: true
647 | AlsoCouldBeApply:
648 | active: false
649 | BracesOnIfStatements:
650 | active: false
651 | singleLine: "never"
652 | multiLine: "always"
653 | BracesOnWhenStatements:
654 | active: false
655 | singleLine: "necessary"
656 | multiLine: "consistent"
657 | CanBeNonNullable:
658 | active: false
659 | CascadingCallWrapping:
660 | active: false
661 | includeElvis: true
662 | ClassOrdering:
663 | active: false
664 | CollapsibleIfStatements:
665 | active: false
666 | DataClassContainsFunctions:
667 | active: false
668 | conversionFunctionPrefix:
669 | - "to"
670 | allowOperators: false
671 | DataClassShouldBeImmutable:
672 | active: false
673 | DestructuringDeclarationWithTooManyEntries:
674 | active: true
675 | maxDestructuringEntries: 3
676 | DoubleNegativeLambda:
677 | active: false
678 | negativeFunctions:
679 | - reason: "Use `takeIf` instead."
680 | value: "takeUnless"
681 | - reason: "Use `all` instead."
682 | value: "none"
683 | negativeFunctionNameParts:
684 | - "not"
685 | - "non"
686 | EqualsNullCall:
687 | active: true
688 | EqualsOnSignatureLine:
689 | active: false
690 | ExplicitCollectionElementAccessMethod:
691 | active: false
692 | ExplicitItLambdaParameter:
693 | active: true
694 | ExpressionBodySyntax:
695 | active: false
696 | includeLineWrapping: false
697 | ForbiddenAnnotation:
698 | active: false
699 | annotations:
700 | - reason: "it is a java annotation. Use `Suppress` instead."
701 | value: "java.lang.SuppressWarnings"
702 | - reason: "it is a java annotation. Use `kotlin.Deprecated` instead."
703 | value: "java.lang.Deprecated"
704 | - reason: "it is a java annotation. Use `kotlin.annotation.MustBeDocumented` instead."
705 | value: "java.lang.annotation.Documented"
706 | - reason: "it is a java annotation. Use `kotlin.annotation.Target` instead."
707 | value: "java.lang.annotation.Target"
708 | - reason: "it is a java annotation. Use `kotlin.annotation.Retention` instead."
709 | value: "java.lang.annotation.Retention"
710 | - reason: "it is a java annotation. Use `kotlin.annotation.Repeatable` instead."
711 | value: "java.lang.annotation.Repeatable"
712 | - reason: "Kotlin does not support @Inherited annotation, see https://youtrack.jetbrains.com/issue/KT-22265"
713 | value: "java.lang.annotation.Inherited"
714 | ForbiddenComment:
715 | active: true
716 | comments:
717 | - reason: "Forbidden FIXME todo marker in comment, please fix the problem."
718 | value: "FIXME:"
719 | - reason: "Forbidden STOPSHIP todo marker in comment, please address the problem before shipping the code."
720 | value: "STOPSHIP:"
721 | - reason: "Forbidden TODO todo marker in comment, please do the changes."
722 | value: "TODO:"
723 | allowedPatterns: ""
724 | ForbiddenImport:
725 | active: false
726 | imports: []
727 | forbiddenPatterns: ""
728 | ForbiddenMethodCall:
729 | active: false
730 | methods:
731 | - reason: "print does not allow you to configure the output stream. Use a logger instead."
732 | value: "kotlin.io.print"
733 | - reason: "println does not allow you to configure the output stream. Use a logger instead."
734 | value: "kotlin.io.println"
735 | ForbiddenSuppress:
736 | active: false
737 | rules: []
738 | ForbiddenVoid:
739 | active: true
740 | ignoreOverridden: false
741 | ignoreUsageInGenerics: false
742 | FunctionOnlyReturningConstant:
743 | active: true
744 | ignoreOverridableFunction: true
745 | ignoreActualFunction: true
746 | excludedFunctions: []
747 | LoopWithTooManyJumpStatements:
748 | active: true
749 | maxJumpCount: 1
750 | MagicNumber:
751 | active: true
752 | excludes:
753 | [
754 | "**/test/**",
755 | "**/androidTest/**",
756 | "**/commonTest/**",
757 | "**/jvmTest/**",
758 | "**/androidUnitTest/**",
759 | "**/androidInstrumentedTest/**",
760 | "**/jsTest/**",
761 | "**/iosTest/**",
762 | "**/*.kts",
763 | ]
764 | ignoreNumbers:
765 | - "-1"
766 | - "0"
767 | - "1"
768 | - "2"
769 | ignoreHashCodeFunction: true
770 | ignorePropertyDeclaration: false
771 | ignoreLocalVariableDeclaration: false
772 | ignoreConstantDeclaration: true
773 | ignoreCompanionObjectPropertyDeclaration: true
774 | ignoreAnnotation: false
775 | ignoreNamedArgument: true
776 | ignoreEnums: false
777 | ignoreRanges: false
778 | ignoreExtensionFunctions: true
779 | MandatoryBracesLoops:
780 | active: false
781 | MaxChainedCallsOnSameLine:
782 | active: false
783 | maxChainedCalls: 5
784 | MaxLineLength:
785 | active: true
786 | maxLineLength: 120
787 | excludePackageStatements: true
788 | excludeImportStatements: true
789 | excludeCommentStatements: false
790 | excludeRawStrings: true
791 | MayBeConst:
792 | active: true
793 | ModifierOrder:
794 | active: true
795 | MultilineLambdaItParameter:
796 | active: false
797 | MultilineRawStringIndentation:
798 | active: false
799 | indentSize: 4
800 | trimmingMethods:
801 | - "trimIndent"
802 | - "trimMargin"
803 | NestedClassesVisibility:
804 | active: true
805 | NewLineAtEndOfFile:
806 | active: true
807 | NoTabs:
808 | active: false
809 | NullableBooleanCheck:
810 | active: false
811 | ObjectLiteralToLambda:
812 | active: true
813 | OptionalAbstractKeyword:
814 | active: true
815 | OptionalUnit:
816 | active: false
817 | PreferToOverPairSyntax:
818 | active: false
819 | ProtectedMemberInFinalClass:
820 | active: true
821 | RedundantExplicitType:
822 | active: false
823 | RedundantHigherOrderMapUsage:
824 | active: true
825 | RedundantVisibilityModifierRule:
826 | active: false
827 | ReturnCount:
828 | active: true
829 | max: 2
830 | excludedFunctions:
831 | - "equals"
832 | excludeLabeled: false
833 | excludeReturnFromLambda: true
834 | excludeGuardClauses: false
835 | SafeCast:
836 | active: true
837 | SerialVersionUIDInSerializableClass:
838 | active: true
839 | SpacingBetweenPackageAndImports:
840 | active: false
841 | StringShouldBeRawString:
842 | active: false
843 | maxEscapedCharacterCount: 2
844 | ignoredCharacters: []
845 | ThrowsCount:
846 | active: true
847 | max: 2
848 | excludeGuardClauses: false
849 | TrailingWhitespace:
850 | active: false
851 | TrimMultilineRawString:
852 | active: false
853 | trimmingMethods:
854 | - "trimIndent"
855 | - "trimMargin"
856 | UnderscoresInNumericLiterals:
857 | active: false
858 | acceptableLength: 4
859 | allowNonStandardGrouping: false
860 | UnnecessaryAbstractClass:
861 | active: true
862 | UnnecessaryAnnotationUseSiteTarget:
863 | active: false
864 | UnnecessaryApply:
865 | active: true
866 | UnnecessaryBackticks:
867 | active: false
868 | UnnecessaryBracesAroundTrailingLambda:
869 | active: false
870 | UnnecessaryFilter:
871 | active: true
872 | UnnecessaryInheritance:
873 | active: true
874 | UnnecessaryInnerClass:
875 | active: false
876 | UnnecessaryLet:
877 | active: false
878 | UnnecessaryParentheses:
879 | active: false
880 | allowForUnclearPrecedence: false
881 | UntilInsteadOfRangeTo:
882 | active: false
883 | UnusedImports:
884 | active: false
885 | UnusedParameter:
886 | active: true
887 | allowedNames: "ignored|expected"
888 | UnusedPrivateClass:
889 | active: true
890 | UnusedPrivateMember:
891 | active: true
892 | allowedNames: ""
893 | UnusedPrivateProperty:
894 | active: true
895 | allowedNames: "_|ignored|expected|serialVersionUID"
896 | UseAnyOrNoneInsteadOfFind:
897 | active: true
898 | UseArrayLiteralsInAnnotations:
899 | active: true
900 | UseCheckNotNull:
901 | active: true
902 | UseCheckOrError:
903 | active: true
904 | UseDataClass:
905 | active: false
906 | allowVars: false
907 | UseEmptyCounterpart:
908 | active: false
909 | UseIfEmptyOrIfBlank:
910 | active: false
911 | UseIfInsteadOfWhen:
912 | active: false
913 | ignoreWhenContainingVariableDeclaration: false
914 | UseIsNullOrEmpty:
915 | active: true
916 | UseLet:
917 | active: false
918 | UseOrEmpty:
919 | active: true
920 | UseRequire:
921 | active: true
922 | UseRequireNotNull:
923 | active: true
924 | UseSumOfInsteadOfFlatMapSize:
925 | active: false
926 | UselessCallOnNotNull:
927 | active: true
928 | UtilityClassWithPublicConstructor:
929 | active: true
930 | VarCouldBeVal:
931 | active: true
932 | ignoreLateinitVar: false
933 | WildcardImport:
934 | active: true
935 | excludeImports:
936 | - "java.util.*"
937 |
--------------------------------------------------------------------------------