Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 9 additions & 2 deletions .editorconfig
Original file line number Diff line number Diff line change
@@ -1,3 +1,9 @@
[**/build/generated/**]
ktlint = disabled

[**/_template**]
ktlint = disabled

[*.{kt,kts}]

max_line_length = 140
Expand All @@ -11,6 +17,8 @@ ij_kotlin_allow_trailing_comma = true
ij_kotlin_allow_trailing_comma_on_call_site = true

continuation_indent_size = 4

ktlint_code_style = android_studio
ktlint_standard_import-ordering = disabled
ktlint_standard_function-signature = disabled
ktlint_standard_context-receiver-wrapping = disabled
Expand All @@ -19,5 +27,4 @@ ktlint_standard_no-empty-first-line-in-class-body = disabled
ktlint_standard_no-blank-line-in-list = disabled
ktlint_standard_blank-line-before-declaration = disabled
ktlint_standard_annotation = disabled


ktlint_function_naming_ignore_when_annotated_with = Composable
4 changes: 3 additions & 1 deletion .github/workflows/enterprise.yml
Original file line number Diff line number Diff line change
Expand Up @@ -25,4 +25,6 @@ jobs:
SIGNING_KEYSTORE_PASSWORD: android
SIGNING_KEY_ALIAS: androiddebugkey
SIGNING_KEY_PASSWORD: android
APP_DISTRIBUTION_SERVICE_ACCOUNT: ${{ secrets.APP_DISTRIBUTION_SERVICE_ACCOUNT }}
APP_DISTRIBUTION_SERVICE_ACCOUNT: ${{ secrets.APP_DISTRIBUTION_SERVICE_ACCOUNT }}
# TODO Set up `GRADLE_CACHE_ENCRYPTION_KEY` for this GitHub repository
GRADLE_CACHE_ENCRYPTION_KEY: ${{ secrets.GRADLE_CACHE_ENCRYPTION_KEY }}
3 changes: 3 additions & 0 deletions .github/workflows/pull_request.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,6 @@ jobs:
with:
LINT_GRADLE_TASKS: lintCheck
TEST_GRADLE_TASKS: testDevEnterpriseUnitTest
secrets:
# TODO Set up `GRADLE_CACHE_ENCRYPTION_KEY` for this GitHub repository
GRADLE_CACHE_ENCRYPTION_KEY: ${{ secrets.GRADLE_CACHE_ENCRYPTION_KEY }}
15 changes: 7 additions & 8 deletions app/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import org.jetbrains.kotlin.gradle.dsl.JvmTarget

plugins {
alias(libs.plugins.android.application)
alias(libs.plugins.kotlin.android)
Expand Down Expand Up @@ -50,12 +52,8 @@ android {

compileOptions {
isCoreLibraryDesugaringEnabled = true
sourceCompatibility = JavaVersion.VERSION_17
targetCompatibility = JavaVersion.VERSION_17
}

kotlinOptions.apply {
jvmTarget = JavaVersion.VERSION_17.toString()
sourceCompatibility = ProjectSettings.JavaCompatibility
targetCompatibility = ProjectSettings.JavaCompatibility
}

sourceSets {
Expand Down Expand Up @@ -132,10 +130,11 @@ android {
}

kotlin {
jvmToolchain(JavaVersion.VERSION_17.majorVersion.toInt())
jvmToolchain(ProjectSettings.JvmToolchainVersion)

compilerOptions {
optIn.add("kotlin.RequiresOptIn")
jvmTarget = JvmTarget.fromTarget(ProjectSettings.KotlinJvmTargetNum)
}
}

Expand Down Expand Up @@ -181,12 +180,12 @@ dependencies {
implementation(libs.okHttp)
implementation(libs.logging)
implementation(libs.retrofit)
implementation(libs.retrofit.converter)
implementation(libs.coil)
implementation(libs.coil.network)

// Serialization
implementation(libs.serialization.json)
implementation(libs.serialization.converter)

// Other
implementation(libs.timber)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,7 @@ import javax.inject.Singleton
* Uses [kotlinx.serialization] to serialize and deserialize objects into Strings.
*/
@Singleton
class JsonPersistence @Inject constructor(
val dataStore: DataStore<Preferences>,
val json: Json,
) {
class JsonPersistence @Inject constructor(val dataStore: DataStore<Preferences>, val json: Json) {

/**
* Saves provided [value] in persistence under provided [key].
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,7 @@ import javax.inject.Singleton
* [DataStore]-backed Persistence which allows storage and observing of persisted entities.
*/
@Singleton
class PrimitivePersistence @Inject constructor(
private val dataStore: DataStore<Preferences>,
) {
class PrimitivePersistence @Inject constructor(private val dataStore: DataStore<Preferences>) {

suspend fun <T : Any> get(key: Preferences.Key<T>): T? = dataStore.data.firstOrNull()?.run {
this[key]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,5 @@ interface ApiService {
suspend fun user(): SampleApiModel

@Serializable
data class SampleApiModel(
val id: String,
@Contextual val dateTime: ZonedDateTime,
)
data class SampleApiModel(val id: String, @Contextual val dateTime: ZonedDateTime)
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ import kotlinx.serialization.modules.SerializersModule
@InstallIn(SingletonComponent::class)
class ApplicationModule {
private val Context.dataStore: DataStore<Preferences> by preferencesDataStore(
name = Constants.DataStore.DEFAULT_DATASTORE_NAME
name = Constants.DataStore.DEFAULT_DATASTORE_NAME,
)

@Provides
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import app.futured.androidprojecttemplate.BuildConfig
import app.futured.androidprojecttemplate.data.remote.ApiService
import app.futured.androidprojecttemplate.tools.Constants.Api.BASE_PROD_URL
import app.futured.androidprojecttemplate.tools.Constants.Api.TIMEOUT_IN_SECONDS
import com.jakewharton.retrofit2.converter.kotlinx.serialization.asConverterFactory
import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
Expand All @@ -15,6 +14,7 @@ import okhttp3.MediaType.Companion.toMediaType
import okhttp3.OkHttpClient
import okhttp3.logging.HttpLoggingInterceptor
import retrofit2.Retrofit
import retrofit2.converter.kotlinx.serialization.asConverterFactory
import timber.log.Timber
import java.util.concurrent.TimeUnit
import javax.inject.Singleton
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,20 +38,19 @@ sealed class Destination(
data object Detail : Destination(
route = "detail/{title}?subtitle={subtitle}?value={value}",
destinationScreen = { DetailScreen(navigation = it) },
arguments =
listOf(
navArgument("title") {
type = NavType.StringType
},
navArgument("subtitle") {
type = NavType.StringType
defaultValue = "Default subtitle"
},
navArgument("value") {
type = NavType.StringType
nullable = true
},
),
arguments = listOf(
navArgument("title") {
type = NavType.StringType
},
navArgument("subtitle") {
type = NavType.StringType
defaultValue = "Default subtitle"
},
navArgument("value") {
type = NavType.StringType
nullable = true
},
),
) {
fun buildRoute(title: String, subtitle: String?, value: String?): String = route
.withArgument("title", title)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,9 @@ import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.receiveAsFlow
import kotlinx.coroutines.launch

abstract class BaseViewModel<VS : ViewState> : ViewModel(), CoroutineScopeOwner {
abstract class BaseViewModel<VS : ViewState> :
ViewModel(),
CoroutineScopeOwner {
abstract val viewState: VS

override val coroutineScope: CoroutineScope = viewModelScope
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,11 @@ inline fun <reified T : Any> withNonNullValue(receiver: T?, block: (T) -> Unit)
if (receiver != null) block(receiver)
}

inline fun <T> safe(block: () -> T): T? {
return try {
block()
} catch (e: Exception) {
e.printStackTrace()
null
}
inline fun <T> safe(block: () -> T): T? = try {
block()
} catch (e: Exception) {
e.printStackTrace()
null
}

fun <T> T?.orThrow(): T = this ?: error("UnexpectedError") // Dev error
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
@file:OptIn(ExperimentalMaterial3Api::class)
@file:Suppress("PackageNaming")

package app.futured.androidprojecttemplate.ui.screens._templateScreen

Expand Down Expand Up @@ -111,9 +110,9 @@ sealed class TEMPLATEEvent : Event<TEMPLATEViewState>()
data object NavigateBackEvent : TEMPLATEEvent()

@HiltViewModel
class TEMPLATEViewModel @Inject constructor(
override val viewState: TEMPLATEViewState,
) : BaseViewModel<TEMPLATEViewState>(), TEMPLATE.Actions {
class TEMPLATEViewModel @Inject constructor(override val viewState: TEMPLATEViewState) :
BaseViewModel<TEMPLATEViewState>(),
TEMPLATE.Actions {
override fun onNavigateBack() {
sendEvent(NavigateBackEvent)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,9 @@ import dagger.hilt.android.lifecycle.HiltViewModel
import javax.inject.Inject

@HiltViewModel
class DetailViewModel @Inject constructor(
override val viewState: DetailViewState,
) : BaseViewModel<DetailViewState>(), Detail.Actions {
class DetailViewModel @Inject constructor(override val viewState: DetailViewState) :
BaseViewModel<DetailViewState>(),
Detail.Actions {
override fun onNavigateBack() {
sendEvent(NavigateBackEvent)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,9 @@ import dagger.hilt.android.lifecycle.HiltViewModel
import javax.inject.Inject

@HiltViewModel
class HomeViewModel @Inject constructor(
override val viewState: HomeViewState,
) : BaseViewModel<HomeViewState>(), Home.Actions {
class HomeViewModel @Inject constructor(override val viewState: HomeViewState) :
BaseViewModel<HomeViewState>(),
Home.Actions {
override fun onIncrementCounter() {
viewState.counter++
}
Expand Down
6 changes: 6 additions & 0 deletions buildSrc/src/main/kotlin/ProjectSettings.kt
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import org.gradle.api.JavaVersion

object ProjectSettings {
const val applicationId = "app.futured.androidprojecttemplate"
const val compileSdkVersion = 35
Expand All @@ -6,6 +8,10 @@ object ProjectSettings {
val versionName = System.getenv("ANDROID_VERSION_NAME") ?: "1.0.0"
val versionCode = System.getenv("ANDROID_BUILD_NUMBER")?.toIntOrNull() ?: 1

val JavaCompatibility = JavaVersion.VERSION_17
val KotlinJvmTargetNum = JavaCompatibility.majorVersion
val JvmToolchainVersion = KotlinJvmTargetNum.toInt()

object Gradle {
const val TaskGroup = "futured"
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,12 @@ open class LintCheck : DefaultTask() {
.forEach {
dependsOn("${it.path}:detekt")
}

project.subprojects
.filter { it.plugins.hasPlugin("com.android.library") || it.plugins.hasPlugin("com.android.application") }
.forEach {
dependsOn("${it.path}:lintDevRelease")
}
}
}
}
17 changes: 12 additions & 5 deletions gradle.properties
Original file line number Diff line number Diff line change
@@ -1,7 +1,14 @@
org.gradle.jvmargs=-Xmx4g
#Gradle
org.gradle.jvmargs=-Xmx6g -Xms256m -Dfile.encoding=UTF-8 -XX:+UseParallelGC -XX:MaxMetaspaceSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dkotlin.daemon.jvm.options=-XX:MaxMetaspaceSize=1g"
org.gradle.parallel=true
org.gradle.caching=true
org.gradle.configureondemand=true
org.gradle.configuration-cache=true
# Use this flag carefully, in case some of the plugins are not fully compatible.
org.gradle.configuration-cache.problems=warn

android.useAndroidX=true

kapt.incremental.apt=true
#Kotlin
kotlin.code.style=official

kapt.include.compile.classpath=false
#Android
android.useAndroidX=true
50 changes: 22 additions & 28 deletions gradle/libs.versions.toml
Original file line number Diff line number Diff line change
@@ -1,42 +1,37 @@
[versions]
# https://kotlinlang.org/docs/multiplatform-compatibility-guide.html#version-compatibility
agp = "8.8.1"
agp = "8.11.0"
gradleVersionsPlugin = "0.50.0"
kotlin = "2.1.0"
ksp = "2.1.0-1.0.29" # Must be compatible with: `kotlin`
androidxComposeBom = "2024.12.01"
androidxActivity = "1.7.2"
androidxSplashScreen = "1.2.0-alpha01"
hilt = "2.55"
kotlin = "2.2.0"
ksp = "2.2.0-2.0.2" # Must be compatible with: `kotlin`
androidxComposeBom = "2025.06.01"
hilt = "2.56.2"
arkitekt = "5.2.2"
timber = "5.0.1"
testCore = "1.6.1"
testRunner = "1.5.2"
testRunner = "1.6.2"
junit = "1.2.1"
mockk = "1.13.8"
serializationJson = "1.6.3"
serializationConverter = "1.0.0"
mockk = "1.14.4"
serializationJson = "1.8.1"
okhttp = "4.12.0"
retrofit = "2.11.0"
navigation = "2.8.5"
retrofit = "3.0.0"
navigation = "2.9.0"
hiltNavigation = "1.2.0"
composeLint = "1.4.2"
androidx-activity-compose = "1.9.3"
jdkDesugaring = "2.1.4"
androidx = "1.15.0"
appcompat = "1.7.0"
lifecycle = "2.8.7"
preference = "1.2.1"
androidx-activity-compose = "1.10.1"
jdkDesugaring = "2.1.5"
androidx = "1.16.0"
appcompat = "1.7.1"
lifecycle = "2.9.1"
activity = "1.9.0"
detekt = "1.23.6"
ktlintGradle = "12.1.2"
ktlint = "1.2.1"
detekt = "1.23.8"
ktlintGradle = "12.3.0"
ktlint = "1.6.0"
google-servicesPlugin = "4.4.2"
google-firebaseAppDistributionPlugin = "5.0.0"
google-firebaseAppDistributionPlugin = "5.1.1"
sheethappens = "1.0.3"
splash-screen = "1.0.1"
coil = "3.0.4"
datastore = "1.1.2"
coil = "3.2.0"
datastore = "1.1.7"

[libraries]

Expand All @@ -62,14 +57,12 @@ appcompat = { group = "androidx.appcompat", name = "appcompat", version.ref = "a
lifecycle-viewmodel = { group = "androidx.lifecycle", name = "lifecycle-viewmodel-ktx", version.ref = "lifecycle" }
lifecycle-runtime = { group = "androidx.lifecycle", name = "lifecycle-runtime", version.ref = "lifecycle" }
activity-ktx = { group = "androidx.activity", name = "activity-ktx", version.ref = "activity" }
androidx-preferences = { group = "androidx.preference", name = "preference-ktx", version.ref = "preference" }
androidx-datastore-preferences = { group = "androidx.datastore", name = "datastore-preferences", version.ref = "datastore" }

androidx-splashscreen = { group = "androidx.core", name = "core-splashscreen", version.ref = "splash-screen" }

# Serialization
serialization-json = { group = "org.jetbrains.kotlinx", name = "kotlinx-serialization-json", version.ref = "serializationJson" }
serialization-converter = { group = "com.jakewharton.retrofit", name = "retrofit2-kotlinx-serialization-converter", version.ref = "serializationConverter" }

# MVVM
arkitekt-usecases = { group = "app.futured.arkitekt", name = "cr-usecases", version.ref = "arkitekt" }
Expand All @@ -87,6 +80,7 @@ test-junit = { group = "androidx.test.ext", name = "junit-ktx", version.ref = "j
okHttp = { group = "com.squareup.okhttp3", name = "okhttp", version.ref = "okhttp" }
logging = { group = "com.squareup.okhttp3", name = "logging-interceptor", version.ref = "okhttp" }
retrofit = { group = "com.squareup.retrofit2", name = "retrofit", version.ref = "retrofit" }
retrofit-converter = { group = "com.squareup.retrofit2", name = "converter-kotlinx-serialization", version.ref = "retrofit" }
coil = { group = "io.coil-kt.coil3", name = "coil-compose", version.ref = "coil" }
coil-network = { group = "io.coil-kt.coil3", name = "coil-network-okhttp", version.ref = "coil" }

Expand Down
Loading
Loading