MacOS specific code with Compose Multiplatform - macos

I'm using Compose Multiplatform to write an app for MacOS. But I want to check if I have an internet connection. Preferably unmetered (so no hotspot). I have found some code for MacOS to check that. But when I create an expect class it complains about the actual class for jvmMain. But I want to put that part in macOSMain. If I try to add an empty class in jvmMain it takes that class instead of macOSMain. So I'm not sure how to implement this?
Here is a part of my build.gradle.kts:
kotlin {
jvm {
compilations.all {
kotlinOptions.jvmTarget = "11"
}
withJava()
}
macosArm64("macOS")
sourceSets {
val commonMain by getting {
dependencies {
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.4")
}
}
val jvmMain by getting {
dependsOn(commonMain)
dependencies {
implementation(compose.desktop.currentOs)
}
}
val macOSMain by getting {
dependsOn(commonMain)
}
}
}
In commonMain I put this for testing:
expect class Test {
fun test(): String
}
In macOSMain I put this:
actual class Test {
actual fun test(): String {
return "MacOS"
}
}
And in jvmMain I put this:
actual class Test {
actual fun test(): String {
return "JVM"
}
}
And to call the function in jvmMain:
fun main() = application {
println("test: ${Test().test()}")
Window(onCloseRequest = ::exitApplication) {
App()
}
}
Which unfortunately prints "JVM". What do I need to do to make it print "MacOS"? Preferably without implementing the class in jvmMain. I also tried expect class in jvmMain, but it then complained about the function not being there in jvmMain.

Related

Configure default Kotlin coroutine context in Spring MVC

I need to configure default coroutine context for all requests in Spring MVC. For example MDCContext (similar question as this but for MVC not WebFlux).
What I have tried
Hook into Spring - the coroutine code is here but there is no way to change the default behavior (need to change InvocableHandlerMethod.doInvoke implementation)
Use AOP - AOP and coroutines do not play well together
Any ideas?
This seems to work:
#Configuration
class ContextConfig: WebMvcRegistrations {
override fun getRequestMappingHandlerAdapter(): RequestMappingHandlerAdapter {
return object: RequestMappingHandlerAdapter() {
override fun createInvocableHandlerMethod(handlerMethod: HandlerMethod): ServletInvocableHandlerMethod {
return object : ServletInvocableHandlerMethod(handlerMethod) {
override fun doInvoke(vararg args: Any?): Any? {
val method = bridgedMethod
ReflectionUtils.makeAccessible(method)
if (KotlinDetector.isSuspendingFunction(method)) {
// Exception handling skipped for brevity, copy it from super.doInvoke()
return invokeSuspendingFunctionX(method, bean, *args)
}
return super.doInvoke(*args)
}
/**
* Copied from CoroutinesUtils in order to be able to set CoroutineContext
*/
#Suppress("UNCHECKED_CAST")
private fun invokeSuspendingFunctionX(method: Method, target: Any, vararg args: Any?): Publisher<*> {
val function = method.kotlinFunction!!
val mono = mono(YOUR_CONTEXT_HERE) {
function.callSuspend(target, *args.sliceArray(0..(args.size-2))).let { if (it == Unit) null else it }
}.onErrorMap(InvocationTargetException::class.java) { it.targetException }
return if (function.returnType.classifier == Flow::class) {
mono.flatMapMany { (it as Flow<Any>).asFlux() }
}
else {
mono
}
}
}
}
}
}
}

`runBlocking` coroutine builder is not resolved in the project (Other builders are resolved)

As the title suggest, the coroutine builder runBlocking is missing in the coroutine liblary I just added in my build.gradle. Funny thing is every other thing appears to be available, GlobalScope, CoroutineScope.launch CoroutineScope.async all present. runBlocking isn't. What am I doing wrong?
here is my build.gradle
buildscript {
ext {
ktor_version = "1.1.1"
kotlin_version = "1.3.20-eap-52"
}
dependencies {
classpath "org.jetbrains.kotlin:kotlin-frontend-plugin:0.0.44"
classpath "org.jetbrains.kotlin:kotlin-serialization:$kotlin_version"
}
}
plugins {
id 'kotlin-multiplatform' version '1.3.20-eap-100'
}
repositories {
maven { url 'https://dl.bintray.com/kotlin/kotlin-eap' }
maven { url 'https://dl.bintray.com/kotlin/kotlin-js-wrappers' }
maven { url 'https://dl.bintray.com/kotlinx/kotlinx' }
maven { url "https://kotlin.bintray.com/kotlinx" }
jcenter()
mavenCentral()
}
group 'books'
version '0.0.0'
apply plugin: 'maven-publish'
apply plugin: "org.jetbrains.kotlin.frontend"
kotlin {
jvm() {
compilations.all {
tasks[compileKotlinTaskName].kotlinOptions {
jvmTarget = "1.8"
}
}
}
js() {
compilations.all {
tasks[compileKotlinTaskName].kotlinOptions {
def optDir = compileKotlinTaskName.contains("Test") ? "test/${project.name}.test.js" : "main/${project.name}.js"
kotlinOptions.metaInfo = true
kotlinOptions.outputFile = "$project.buildDir.path/js/$optDir"
kotlinOptions.sourceMap = true
kotlinOptions.moduleKind = 'commonjs'
kotlinOptions.main = "call"
}
}
}
sourceSets {
commonMain {
dependencies {
implementation kotlin('stdlib-common')
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core-common:$ktor_version"
}
}
commonTest {
dependsOn commonMain
dependencies {
implementation kotlin('test-common')
implementation kotlin('test-annotations-common')
}
}
jvmMain {
dependencies {
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:$ktor_version"
implementation kotlin('stdlib-jdk8')
}
}
jvmTest {
dependsOn jvmMain
dependencies {
implementation kotlin('test')
implementation kotlin('test-junit')
}
}
jsMain {
dependencies {
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core-js:$ktor_version"
implementation kotlin('stdlib-js')
}
}
jsTest {
dependsOn jsMain
dependencies {
implementation kotlin('test-js')
}
}
}
}
task runJest(type: Exec) {
group = "verification"
commandLine "sh", "runJest.sh"
}
runJest.dependsOn(jsTest)
task testAll() {
group = "verification"
dependsOn(jvmTest, runJest)
}
kotlinFrontend {
npm {
devDependency("karma")
}
sourceMaps = true
webpackBundle {
bundleName = "main"
host = "0.0.0.0"
contentPath = file("$buildDir.path/resources/main")
}
}
With that gradle configuration, I have been able to write tests well (Learning TDD) with kotlin-multiplatform. And here is my sample below
import kotlin.test.*
import com.luge.books.*
import kotlinx.coroutines.*
class BookTest {
#BeforeTest
fun setup() {
val book = Book()
}
#Test
fun testingInstantiation() {
val book = Book()
assertEquals(book.year, 1990, "Books do match the year")
}
#Test
fun willFail() {
assertFalse(false)
}
#Test
fun testingCoroutines() {
val job = GlobalScope.launch {
delay(5000)
println("Doing stuff")
assertTrue(false)
}
}
}
If you look closely, the test testingCoroutines passes, but since I am launching from the GlobalScope, it just fires and forgets and the test returns without throwing any error. If I incoporate runBlocking, the IDE highlights it with red color (you know, as something it doesn't understant), end even the kotlin compiler shouts, unresolved reference runBlockin. Help please....
After struggling here and there, I finally knew that runBlocking is only available in kotlin/jvm. So, it is not in kotlin/js or kotlin/common.
Just for future references, if you want to run multiplatform tests, then use this work around

MockK - verify failing with arguments not matching

I've noticed that sometimes verify fails with "... call to ... happened, but arguments are not matching"
Here is a sample test that shows verify failing:
class TestStuff {
val stuff = "1"
#RelaxedMockK
lateinit var testService: TestService
#RelaxedMockK
lateinit var testInterface: TestInterface
#Before
fun setup() {
MockKAnnotations.init(this)
every { testInterface.testStuff } returns stuff
}
#Test
fun testStuffCalled() {
testService.testStuff(testInterface.testStuff)
verify { testService.testStuff(testInterface.testStuff) }
}
}
interface TestInterface {
val testStuff: String
}
class TestService {
fun testStuff(stuff: String) {
}
}
If I change the line with the verify call to the following 2 lines, then it works:
let testStuffCopy = testInterface.testStuff
verify { testService.testStuff(testStuffCopy) }
I'm unsure if this is a bug, but a quick workaround would be to use stuff as the verification, as you want the returned value to be it:
verify { testService.testStuff(stuff) }
This way you still test that the behaviour was called, and as you mocked the return of testInterface to return stuff, this should work.
I created an Issue in Mockk for this, and I'll update this answer when something is updated there.

How to implement this 'dynamic block' in Gradle?

I am new to gradle. So let straight to the point. I want to implement the block as below. Note that the libraries is dynamic, and available for other developers to add on for the needed libraries.
libraries {
slf4j 'org.slf4j:slf4j-api:1.7.21'
junit 'junit:junit:4.12'
}
So that I can call them out like this.
dependencies {
compile libraries.slf4j
testCompile libraries.junit
}
I am not sure how to make it. But I found some related solution from here. As shown below:
apply plugin: GreetingPlugin
greeting {
message 'Hi'
greeter 'Gradle'
}
class GreetingPlugin implements Plugin<Project> {
void apply(Project project) {
project.extensions.create("greeting", GreetingPluginExtension)
project.task('hello') {
doLast {
println "${project.greeting.message} from ${project.greeting.greeter}"
}
}
}
}
class GreetingPluginExtension {
String message
String greeter
}
The problem is as I add on to the greeting block, I need to declare them in GreetingPluginExtension as well. Any idea how to make it such that only update on greeting block?
What you need to do is to utilize groovy meta programming. Below you can find just a sample, however fully functional.
apply plugin: LibrariesPlugin
libraries {
slf4j 'org.slf4j:slf4j-api:1.7.21'
junit 'junit:junit:4.12'
}
class LibrariesPlugin implements Plugin<Project> {
void apply(Project project) {
project.extensions.create("libraries", LibrariesPluginExtension)
project.task('printLib') {
doLast {
println "$project.libraries.slf4j"
println "$project.libraries.junit"
project.libraries.each {
println "$it.key -> $it.value"
}
}
}
}
}
class LibrariesPluginExtension {
Map libraries = [:]
def methodMissing(String name, args) {
// TODO you need to do some arg checking here
libraries[name] = args[0]
}
def propertyMissing(String name) {
// TODO same here
libraries[name]
}
Iterator iterator() {
libraries.iterator()
}
}

Can a groovy build script class acces the Gradle project directly?

Can a groovy class (located in buildSrc/src/main/groovy) access the project directly, or does the project have to be passed in explicitly?
I am able to access the project by explicitly passing it in as a method parameter, but I do not want to have to pass it in. For an example, I would like to be able to get access to the project via a static method call. Is this type of implicit access possible?
Explicit Access
import org.gradle.api.Project
class MyClazz {
static void foo(Project project) {
println project.version
}
}
Task in build.gradle
task foo() << {
MyClazz.foo(project)
}
Implicit Access via Static Method Call (this is the desired access pattern)
import org.gradle.api.Project
class MyClazz {
static void foo() {
println Project.getProject().version
}
}
Task in build.gradle
task foo() << {
MyClazz.foo()
}
You can use Groovy extension methods to do this.
here's a self-contained example, but should work with Gradle too:
class Project {
// we add this method dynamically
//static getProject() { [ version: 2.3 ] }
}
class MyClazz {
static void foo() {
println Project.getProject().version
}
}
class Gradle {
static def main(args) {
Project.metaClass.static.getProject = { [ version: 4.2 ] }
MyClazz.foo()
}
}

Resources