I used this link to create our own custom lint rules
link
then create WrongLayoutDetector.class to detect wrong layout name
private val allowedPrefixes = listOf("activity_", "view_", "fragment_", "dialog_", "bottom_sheet_", "adapter_item_", "divider_", "space_", "popup_window_")
val ISSUE_WRONG_LAYOUT_NAME = Issue.create("WrongLayoutName",
"Layout names should be prefixed accordingly.",
"The layout file name should be prefixed with one of the following: ${allowedPrefixes.joinToString()}. This will improve consistency in your code base as well as enforce a certain structure.",
CORRECTNESS, 7, WARNING,
Implementation(WrongLayoutNameDetector::class.java, RESOURCE_FILE_SCOPE))
class WrongLayoutNameDetector : LayoutDetector() {
override fun visitDocument(context: XmlContext, document: Document) {
val modified = allowedPrefixes.map {
val resourcePrefix = context.project.resourcePrefix()
.forceUnderscoreIfNeeded()
if (resourcePrefix != it) resourcePrefix + it else it
}
val doesNotStartWithPrefix = modified.none { context.file.name.startsWith(it) }
val notEquals = modified.map {
it.dropLast(1) // Drop the trailing underscore.
}.none { context.file.name == "$it.xml" }
if (doesNotStartWithPrefix && notEquals) {
context.report(ISSUE_WRONG_LAYOUT_NAME, document, context.getLocation(document), "Layout does not start with one of the following prefixes: ${modified.joinToString()}")
}
}
}
private fun String.forceUnderscoreIfNeeded() = if (isNotEmpty() && !endsWith("_")) plus("_") else this
fun Project.resourcePrefix() = if (isGradleProject) computeResourcePrefix(gradleProjectModel).orEmpty() else ""
IssueRegistry class
class IssueRegistry : IssueRegistry() {
override val issues: List<Issue>
get() = listOf( ISSUE_WRONG_LAYOUT_NAME,ISSUE_ALERT_DIALOG_USAGE)
override val api: Int = com.android.tools.lint.detector.api.CURRENT_API
}
build gradle file
apply plugin: 'java-library'
dependencies {
// For a description of the below dependencies, see the main project README
compileOnly "com.android.tools.lint:lint-api:26.2.0-rc02"
compileOnly "com.android.tools.lint:lint-checks:26.2.0-rc02"
testCompile "junit:junit:4.12"
testCompile "com.android.tools.lint:lint:26.2.0-rc02"
testCompile "com.android.tools.lint:lint-tests:26.2.0-rc02"
testCompile "com.android.tools:testutils:26.2.0-rc02"
}
sourceCompatibility = "1.8"
targetCompatibility = "1.8"
jar {
manifest {
// Only use the "-v2" key here if your checks have been updated to the
// new 3.0 APIs (including UAST)
attributes("Lint-Registry-v2": "com.example.lint.checks.IssueRegistry")
}
}
now in our code
lint.xml file
<lint>
<!-- Change the severity of hardcoded strings to "error" -->
<issue id="WrongLayoutName"
severity="warning" />
</lint>
and in app gradle file
lintOptions {
tasks.lint.enabled = true
abortOnError true
lintConfig file('./code_quality_tools/lint.xml')
}
now i am analyze code but not getting any warning like WrongLayoutName.
How could i implement custom lint rules with our app?
if i add <issue id="AllCaps" /> in lint file its working fine and getting warning
You have to add the link-check module in your dependency:
If you have this structure:
lint-check
-- build.gradle
app
-- build.gradle
settings.gradle
In settings.gradle:
include ':app'
include ':lint-checks'
In app/build.gradle:
dependencies {
lintChecks project(':lint-checks')
}
Related
I created a gradle plugin to unify some settings between the various modules of the application. the summary of the error is this:
org.gradle.api.plugins.InvalidPluginException: An exception occurred applying plugin request [id: 'common.plugin']
Caused by: org.gradle.api.UnknownDomainObjectException: Extension of type 'LibraryExtension' does not exist. Currently registered extension types: [ExtraPropertiesExtension,....]
This is a summary image of the project architecture:
CommonPluginClass:
class CommonPluginClass : Plugin<Project> {
private val consumerProguardFileName = "consumer-rules.pro"
private val proguardFileName = "proguard-rules.pro"
private val sdkToCompile = 33
override fun apply(project: Project) {
println(">>> Adding sugar to gradle files!")
with(project)
{
applyPlugins(this)
androidConfig(this)
}
println(">>> Sugar added for core Module!")
}
private fun applyPlugins(project: Project) {
println(">>> apply plugins!")
project.pluginManager.run {
apply("com.android.application")
apply("kotlin-android")
apply("kotlin-kapt")
}
println(">>> end apply plugins!")
}
private fun androidConfig(project: Project) {
project.extensions.configure<LibraryExtension>{
defaultConfig.targetSdk = 33
}
}
}
the error occurs inside the androidConfig function when calling configure
I was inspired by now android, dependencies and imports are similar but not build. Can someone unlock it please.
build-logic:convention build.gradle
plugins {
alias(libs.plugins.kotlin.jvm) apply false
id "org.gradle.kotlin.kotlin-dsl" version "2.4.1"
}
dependencies {
compileOnly(libs.android.pluginGradle)
compileOnly(libs.kotlin.pluginGradle)
}
gradlePlugin {
plugins {
commonPlugin {
id = "common.plugin"
implementationClass = "CommonPluginClass"
}
}
}
LITTLE UPDATE:
I've noticed that any module I enter always identifies it to me as ApplicationExtension
I found the solution and as usual it's a stupid thing.
the application extension use plugin
apply("com.android.application")
the library or module use plugin
apply("com.android.library")
I have two multiplatform modules shared and other in a standard multiplatform template project that targets Android and iOS.
shared defines a class in commonMain source set
class SharedGreeting()
other is setup to depend on shared like this in the gradle file:
val commonMain by getting {
dependencies {
implementation(project(":shared"))
}
}
And in its androidMain sourceset it tries to reference SharedGreeting in some class, fx:
class AndroidGreeter{
val foo = SharedGreeting()
}
But no matter what I try, I get IDE errors when I try to reference the shared class, and I have to manually add an import statement.
The code compiles and deploys without problems though!
Any ideas on what I am missing or misunderstanding? Or is this a bug in KMM?
Full copy of other gradle file:
plugins {
kotlin("multiplatform")
kotlin("native.cocoapods")
id("com.android.library")
}
version = "1.0"
kotlin {
android()
iosX64()
iosArm64()
iosSimulatorArm64()
cocoapods {
summary = "Some description for the Shared Module"
homepage = "Link to the Shared Module homepage"
ios.deploymentTarget = "14.1"
framework {
baseName = "other"
}
}
sourceSets {
val commonMain by getting {
dependencies {
implementation(project(":shared"))
}
}
val commonTest by getting {
dependencies {
implementation(kotlin("test"))
}
}
val androidMain by getting
val androidTest by getting
val iosX64Main by getting
val iosArm64Main by getting
val iosSimulatorArm64Main by getting
val iosMain by creating {
dependsOn(commonMain)
iosX64Main.dependsOn(this)
iosArm64Main.dependsOn(this)
iosSimulatorArm64Main.dependsOn(this)
}
val iosX64Test by getting
val iosArm64Test by getting
val iosSimulatorArm64Test by getting
val iosTest by creating {
dependsOn(commonTest)
iosX64Test.dependsOn(this)
iosArm64Test.dependsOn(this)
iosSimulatorArm64Test.dependsOn(this)
}
}
}
android {
compileSdk = 32
sourceSets["main"].manifest.srcFile("src/androidMain/AndroidManifest.xml")
defaultConfig {
minSdk = 24
targetSdk = 32
}
}
For full project source code:
https://github.com/RabieJradi/kmm_import_error_sample
IDE suggested action for adding a dependency doesn't unfortunately do anything.
Turns out it is a bug in KMM and a fix have been implemented by the Jetbrains team here https://youtrack.jetbrains.com/issue/KTIJ-22056/KMM-Cant-reference-shared-module-commonMain-source-set-classes-from-another-modules-androidMain-source-set
You cannot do this in that way.
To make your project work you have to work with expect and actual words.
So, make these changes:
change your SharedGreeting class in shared module like that:
expect class SharedGreeting {
fun greeting(): String
}
Then your IDE wants you to make two changes, in shared module.
Add a class in both modules, iosMain and androidMain named SharedGreeting. The code is the same for both classes:
actual class SharedGreeting {
actual fun greeting(): String {
return "Hello, ${Platform().platform}!"
}
}
The work is done, now your other library has no errors. From your androidMain module inside "other" you can work only on other androidMain modules inside other MKK libraries.
I was develoing an KMM App, where I try to implement a regular localDatasource and remoteDatasource, using SQLDelight and Ktor respectively.
My problems comes when I try to shared the native code from AndroidApp and iosMain, into commonModule. I start to get the following error into my commonModule expect class:
Expected function 'cache' has no actual declaration in module KMM-APP.shared for JVM
Expected function 'cache' has no actual declaration in module KMM-APP.shared.iosArm64Main for Native
Expected function 'cache' has no actual declaration in module KMM-APP.shared.iosX64Main for Native
It's a bit confuse, in order I don't make use of jvm module in my proyect, although I do for IOS module.
Here it's my cacheAndroid.kt of AndroidApp module:
import android.content.Context
import com.example.kmp_app.db.PetsDatabase
import com.squareup.sqldelight.android.AndroidSqliteDriver
lateinit var appContext: Context
internal actual fun cache(): PetsDatabase {
val driver = AndroidSqliteDriver(PetsDatabase.Schema, appContext, "petsDB.db")
return PetsDatabase(driver)
}
Here is the classes of my IOS module:
import com.example.kmp_app.db.PetsDatabase
import com.squareup.sqldelight.drivers.native.NativeSqliteDriver
internal actual fun cache(): PetsDatabase {
val driver = NativeSqliteDriver(PetsDatabase.Schema, "petsDB.db")
return PetsDatabase(driver)
}
And the use into commonModule:
internal expect fun cache(): PetsDatabase
I in this last line of code where I reciving the error above, but I also get the error into the actual classes of Android and IOS modules, into their expect class variant.
Finally regarding my build.gradle(common)
plugins {
kotlin("multiplatform")
kotlin("native.cocoapods")
id("com.android.library")
id("kotlinx-serialization")
id("com.squareup.sqldelight")
}
version = "1.0"
kotlin {
targets{
ios {
binaries {
framework {
baseName = "shared"
}
}
}
// Block from https://github.com/cashapp/sqldelight/issues/2044#issuecomment-721299517.
val onPhone = System.getenv("SDK_NAME")?.startsWith("iphoneos") ?: false
if (onPhone) {
iosArm64("ios")
} else {
iosX64("ios")
}
android()
//iosSimulatorArm64() sure all ios dependencies support this target
}
cocoapods {
summary = "Some description for the Shared Module"
homepage = "Link to the Shared Module homepage"
ios.deploymentTarget = "14.1"
podfile = project.file("../iosApp/Podfile")
}
sourceSets {
all {
languageSettings.apply {
useExperimentalAnnotation("kotlinx.coroutines.ExperimentalCoroutinesApi")
}
}
val commonMain by getting{
dependencies {
implementation(kotlin("stdlib-common"))
implementation(Coroutines.Core.core)
implementation(Ktor.Core.common)
implementation(Ktor.Json.common)
implementation(Ktor.Logging.common)
implementation(Ktor.Serialization.common)
implementation(SqlDelight.runtime)
}
}
val commonTest by getting {
dependencies {
implementation(kotlin("test-common"))
implementation(kotlin("test-annotations-common"))
implementation(Ktor.Mock.common)
}
}
val androidMain by getting{
dependencies {
implementation(kotlin("stdlib"))
implementation(Coroutines.Core.core)
implementation(Ktor.android)
implementation(Ktor.Core.jvm)
implementation(Ktor.Json.jvm)
implementation(Ktor.Logging.jvm)
implementation(Ktor.Logging.slf4j)
implementation(Ktor.Mock.jvm)
implementation(Ktor.Serialization.jvm)
implementation(Serialization.core)
implementation(SqlDelight.android)
}
}
val androidAndroidTestRelease by getting
val androidTest by getting {
dependsOn(androidAndroidTestRelease)
dependencies {
implementation(kotlin("test-junit"))
implementation("junit:junit:4.13.2")
}
}
val iosX64Main by getting
val iosArm64Main by getting
//val iosSimulatorArm64Main by getting
val ios by creating {
dependsOn(commonMain)
iosX64Main.dependsOn(this)
iosArm64Main.dependsOn(this)
//iosSimulatorArm64Main.dependsOn(this)
dependencies {
implementation(SqlDelight.native)
}
}
}
}
android {
compileSdk = 31
sourceSets["main"].manifest.srcFile("src/androidMain/AndroidManifest.xml")
defaultConfig {
minSdk = 21
targetSdk = 31
versionCode = 1
versionName = "1.0"
}
}
sqldelight {
database("PetsDatabase") {
packageName = "com.example.kmp_app.db"
sourceFolders = listOf("sqldelight")
}
}
And my proyect build.gradle:
buildscript {
repositories {
google()
mavenCentral()
jcenter()
}
dependencies {
classpath("com.android.tools.build:gradle:4.2.0")
classpath(kotlin("gradle-plugin", version = Versions.kotlin))
classpath(kotlin("serialization", version = Versions.kotlin))
classpath("com.squareup.sqldelight:gradle-plugin:${Versions.sqldelight}")
}
}
allprojects {
repositories {
google()
mavenCentral()
jcenter()
}
}
plugins{
//kotlin("android") version "${Versions.kotlin}" apply false
}
I hope you can help and if like this, take thanks in advance !
I think it related with packageName in your Gradle:
packageName = "com.example.kmp_app.db"
Try to pass route of your cache function instead of "com.example.kmp_app.db"
like if my cache function exists on dataSource.cacheSource, we will pass "com.example.kmp_app.db.dataSource.cacheSource"
Be sure your Cache actual / expect function have the same package name like this "com.example.kmp_app.db.dataSource.cacheSource"
Shared gradle
sqldelight {
database("RecipeDatabase") {
packageName = "com.example.food1fork.Food1ForkKmm.DataSource.cacheSource"
sourceFolders = listOf("SqlDelight")
}
}
iOS module
package com.example.food1fork.Food1ForkKmm.DataSource.cacheSource
actual class DriverFactory {
actual fun createDriver(): SqlDriver {
return NativeSqliteDriver(RecipeDatabase.Schema, "recipes.db")
}
}
Android module
package com.example.food1fork.Food1ForkKmm.DataSource.cacheSource
actual class DriverFactory(private val context: Context) {
actual fun createDriver(): SqlDriver {
return AndroidSqliteDriver(RecipeDatabase.Schema, context, "recipes.db")
}
}
Shared module
package com.example.food1fork.Food1ForkKmm.DataSource.cacheSource
expect class DriverFactory {
fun createDriver(): SqlDriver
}
Trying to create some playground over Detekt custom rules and it just doesn't work. Doesn't even try to find the rule-set file.
The gradle goes
plugins {
id 'com.android.application'
id 'kotlin-android'
id("io.gitlab.arturbosch.detekt").version("1.17.1")
}
...
detekt {
toolVersion = "$detekt_version" // 1.17.1
input = files("src/main/java")
config = files("../detekt/detekt-config.yml")
autoCorrect = true
reports { ... }
}
dependencies {
detektPlugins "io.gitlab.arturbosch.detekt:detekt-formatting:$detekt_version"
compileOnly "io.gitlab.arturbosch.detekt:detekt-api:$detekt_version"
detekt "io.gitlab.arturbosch.detekt:detekt-cli:$detekt_version"
...
}
The rule goes
class SomeCustomRule(config: Config) : Rule(config) {
override val issue: Issue
get() = Issue("Import thingy", Severity.Minor, "I don't like this string", Debt(10))
override fun visitImportDirective(importDirective: KtImportDirective) {
val import: String = importDirective.importPath?.pathStr ?: ""
if ("appcompat" in import) {
report(CodeSmell(
issue,
Entity.from(importDirective),
"Importing $import which is an internal import"))
}
}
}
The provider is
class CustomRuleSetProvider : RuleSetProvider {
override val ruleSetId: String = "detekt-custom-rules"
override fun instance(config: Config) = RuleSet(ruleSetId, listOf(SomeCustomRule(config)))
}
And last but not least
At
src/main/resources/META-INF/services/io.gitlab.arturbosch.detekt.api.RuleSetProvider
is
com.playgrounds.detekt.customrule.CustomRuleSetProvider
Still - nothing. I tried to add gibberish at the provider - nothing, no error. I even tried to spoil the declaration at META-INF. Nothing.
Of course I tried to add the provider into my config file, or add a project line in the gradle. Error, not recognised.
What have I missed?
Thanks
I am trying to use Android logs in my shared code so wanted to make use of the 'expected/actual' functionality in order to make the android side use logs to be read in log cat. However I cannot get the android module(not app module) to import the android.util.Log.
I have seen this answer but it did not work for me. I cannot get the import to resolve.
I think I need to implement a specific dependency in order to have access to the import but I'm not sure what that is.
Here is my build.gradle.kts
import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinNativeTarget
plugins {
kotlin("multiplatform")
}
kotlin {
//select iOS target platform depending on the Xcode environment variables
val iOSTarget: (String, KotlinNativeTarget.() -> Unit) -> KotlinNativeTarget =
if (System.getenv("SDK_NAME")?.startsWith("iphoneos") == true)
::iosArm64
else
::iosX64
iOSTarget("ios") {
binaries {
framework {
baseName = "SharedCode"
}
}
}
jvm("android")
sourceSets["commonMain"].dependencies {
implementation("org.jetbrains.kotlin:kotlin-stdlib-common")
implementation("io.ktor:ktor-client:1.0.0-beta-3")
implementation ("org.jetbrains.kotlinx:kotlinx-coroutines-core-common:1.3.2")
// implementation ("org.jetbrains.kotlinx:kotlinx-serialization-runtime-common:0.14.0")
}
sourceSets["androidMain"].dependencies {
implementation("org.jetbrains.kotlin:kotlin-stdlib") //Allows _androidMain to have java imports
implementation("io.ktor:ktor-client-android:1.0.0-beta-3")
api("org.jetbrains.kotlin:kotlin-stdlib:1.3.61")
api("org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.3.61")
implementation("org.jetbrains.kotlin:kotlin-gradle-plugin:1.3.61")
}
}
val packForXcode by tasks.creating(Sync::class) {
val targetDir = File(buildDir, "xcode-frameworks")
/// selecting the right configuration for the iOS
/// framework depending on the environment
/// variables set by Xcode build
val mode = System.getenv("CONFIGURATION") ?: "DEBUG"
val framework = kotlin.targets
.getByName<KotlinNativeTarget>("ios")
.binaries.getFramework(mode)
inputs.property("mode", mode)
dependsOn(framework.linkTask)
from({ framework.outputDirectory })
into(targetDir)
/// generate a helpful ./gradlew wrapper with embedded Java path
doLast {
val gradlew = File(targetDir, "gradlew")
gradlew.writeText("#!/bin/bash\n"
+ "export 'JAVA_HOME=${System.getProperty("java.home")}'\n"
+ "cd '${rootProject.rootDir}'\n"
+ "./gradlew \$#\n")
gradlew.setExecutable(true)
}
}
tasks.getByName("build").dependsOn(packForXcode)
Here you got JVM target with the name "android" instead of actually Android target. The same problem occurred in the linked question. Can you tell, what's going on when you use the script from the answer? It seems like that one should work correctly.
As described in the documentation, one has to use an Android-specific Gradle plugin to make the Android target available. If you want to see how it can be done, consider having a look at this sample.
I had the same problem. Try using android() instead of only the jvm("android").
Also I've added my dependencies to android with android.sourceSets.foreach{ _ ->
dependencies{ ... }
}
Just fixed same issue, finally used this tutorial https://medium.com/icerock/how-to-start-use-kotlin-multiplatform-for-mobile-development-1d3022742178
so my build.gradle looks like:
apply plugin: 'com.android.library'
apply plugin: 'org.jetbrains.kotlin.multiplatform'
android {
compileSdkVersion 29
defaultConfig {
minSdkVersion 14
targetSdkVersion 29
}
}
kotlin {
targets {
android()
iosArm64()
iosX64()
}
sourceSets {
commonMain {
dependencies {
implementation "org.jetbrains.kotlin:kotlin-stdlib-common:$kotlin_version"
}
}
androidMain {
dependencies {
implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
}
}
}
}