https://kotlinlang.org logo
#multiplatform
Title
# multiplatform
c

clark

09/11/2021, 4:34 AM
Hey everyone, I recently started seeing a bug with the
multiplatform-settings
library where anytime I try to init the no arg version in my commonMain code, it throws a
NullPointerException
. See thread for code.
Test code
Copy code
import com.russhwolf.settings.Settings
import kotlin.test.Test

class SettingsTest {

    @Test
    fun testCreateSettings() {
        val settings = Settings()
    }
}
Abbreviated error message
Copy code
java.lang.NullPointerException
	at com.russhwolf.settings.NoArgKt.Settings(NoArg.kt:33)
	at com.envelopemoney.envelopekmm.shared.tests.repository.SettingsTest.testCreateSettings(SettingsTest.kt:10)
.
.
.
Shared module's
build.gradle.kts
Copy code
import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinNativeTarget

plugins {
    kotlin("multiplatform")
    id("com.android.library")
    kotlin("plugin.serialization")
    id("com.apollographql.apollo")
}

android {
    configurations {
        create("androidTestApi")
        create("androidTestDebugApi")
        create("androidTestReleaseApi")
        create("testApi")
        create("testDebugApi")
        create("testReleaseApi")
    }
}

kotlin {
    android()
    ios {
        binaries {
            framework {
                baseName = "shared"
            }
        }
    }

    val coroutinesVersion = "1.5.1-native-mt"
    val serializationVersion = "1.2.2"
    // When you update apollo versions, make sure to update in top level build.gradle.kts too
    val apolloVersion = "2.5.9"
    val kodeinVersion = "7.4.0"
    val multiplatformSettingsVersion = "0.8"
    val kermitVersion = "0.3.0-m1"

    sourceSets {
        val commonMain by getting {
            dependencies {
                implementation(kotlin("stdlib-common"))
                // Coroutines
                implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:$coroutinesVersion") {
                    isForce = true
                }
                // Serialization
                implementation("org.jetbrains.kotlinx:kotlinx-serialization-core:$serializationVersion")
                // Apollo
                implementation("com.apollographql.apollo:apollo-runtime-kotlin:$apolloVersion")
                // Kodein DI
                implementation("org.kodein.di:kodein-di:$kodeinVersion")
                // Shared Preferences
                implementation("com.russhwolf:multiplatform-settings-no-arg:$multiplatformSettingsVersion")
                // Kermit for Logging
                implementation("co.touchlab:kermit:$kermitVersion")
            }
        }
        val commonTest by getting {
            dependencies {
                implementation(kotlin("test-common"))
                implementation(kotlin("test-annotations-common"))
            }
        }
        val androidMain by getting {
            dependencies {
                implementation("com.google.android.material:material:1.4.0")
            }
        }
        val androidTest by getting {
            dependencies {
                implementation(kotlin("test-junit"))
                implementation("junit:junit:4.13.2")
            }
        }
        val iosMain by getting {
            dependencies {
            }
        }
        val iosTest by getting {
            dependencies {
            }
        }
    }
}

android {
    compileSdk = 31
    sourceSets["main"].manifest.srcFile("src/androidMain/AndroidManifest.xml")
    defaultConfig {
        minSdk = 24
        targetSdk = 31
    }
    compileOptions {
        sourceCompatibility(JavaVersion.VERSION_1_8)
        targetCompatibility(JavaVersion.VERSION_1_8)
    }
}

val packForXcode by tasks.creating(Sync::class) {
    group = "build"
    val mode = System.getenv("CONFIGURATION") ?: "DEBUG"
    val sdkName = System.getenv("SDK_NAME") ?: "iphonesimulator"
    val targetName = "ios" + if (sdkName.startsWith("iphoneos")) "Arm64" else "X64"
    val framework =
        kotlin.targets.getByName<KotlinNativeTarget>(targetName).binaries.getFramework(mode)
    inputs.property("mode", mode)
    dependsOn(framework.linkTask)
    val targetDir = File(buildDir, "xcode-frameworks")
    from({ framework.outputDirectory })
    into(targetDir)
}

tasks.getByName("build").dependsOn(packForXcode)

tasks.withType<org.jetbrains.kotlin.gradle.tasks.KotlinCompile>().all {
    kotlinOptions {
        jvmTarget = "1.8"
    }
}

apollo {
    // instruct the compiler to generate Kotlin models
    generateKotlinModels.set(true)
}
Thanks in advance for the help!
r

russhwolf

09/11/2021, 1:50 PM
Well that's not supposed to happen. Throwing at that line implies that androidx,startup isn't doing what I expect it to do, Can you throw a project up somewhere publicly that reproduces the issue?
Oh wait, it’s because you’re doing it in a unit test. no-arg depends on androidx.startup to initialize, which is using a content provider under-the-hood. That doesn’t run in junit
You’ll probably be better off injecting a
Settings
into your tests. If you can’t, you’ll probably need to use on-device tests. If that’s a pain, open an issue and I should be able to expose an API to initialize the no-arg settings manually in tests so it does still work in junit. The issue is that you need this line to run but it’s not accessible except through the androidx.startup machinery, which depends on running on a device.
c

clark

09/11/2021, 3:18 PM
So I started doing it in a unit test to see if I could debug what was happening, though this is also happening when I run on device
r

russhwolf

09/11/2021, 3:24 PM
In that case, can you post a project that reproduces the issue?
c

clark

09/11/2021, 3:24 PM
Ya I'll get something up. Thanks for the help!
Looks like the
multiplatform-settings
construction was not the issue! Thanks for the help, @russhwolf!