viewmodel init fail in release apk since constructor not found - kotlin

I am using viewmodel in jetpack compose and build a factory like
import android.content.Context
import android.util.Log
import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
internal class MyStateBuilder(val args: Map<String, Any?>?, val context: Context) :
ViewModelProvider.Factory {
override fun <T : ViewModel> create(modelClass: Class<T>): T {
try {
Log.d("ppx",modelClass.constructors.size.toString())
modelClass.constructors.forEach {
Log.d("ppx",it.toGenericString())
}
modelClass.getConstructor(Map::class.java, Context::class.java)
return modelClass.getConstructor(Map::class.java, Context::class.java)
.newInstance(args, context)
}catch (e:Exception){
Log.e("ppx","start")
e.printStackTrace()
throw IllegalArgumentException()
}
}
}
these codes works fine at debug compile mode, but when I goto release compile, constructor of provided class suddenly go 0 and viewModel cannot be initialized, does anybody know what happened at compile transmission?

Related

mockito, in koltin how to verify a static method

Having a kotlin singleton static method
internal object TestSingleton {
#JvmStatic
fun staticMethod1 (str: String) {
println("+++ === +++ TestSingleton.staticMethod(), $str")
staticMethod2 (str)
}
#JvmStatic
fun staticMethod2 (str: String) {
println("+++ === +++ TestSingleton.staticMethod2(), $str")
}
}
In java test code:
#Test
public void test_staticMethod() {
try (MockedStatic<TestSingleton> theMock = Mockito.mockStatic(TestSingleton.class, CALLS_REAL_METHODS)) {
TestSingleton.staticMethod1("test");
theMock.verify(() -> TestSingleton.staticMethod2(eq("test")), times(1));
}
}
it runs fine
but convert to kotlin it does not compile:
#Test
open fun test_staticMethod() {
Mockito.mockStatic(TestSingleton::class.java, Mockito.CALLS_REAL_METHODS).use { theMock ->
staticMethod1("test")
theMock.verify(() -> TestSingleton.staticMethod(any(Context.class), "test"), times(1))
// or
theMock.verify(times(1), () -> TestSingleton.staticMethod(any(Context.class)) )
}
}
having mockito version testImplementation "org.mockito:mockito-inline:3.12.4".
How to test static method using mockito in kotlin?
Not tried mockk yet since having a lot tests have been working with mockito. Not sure how simple with mockk in this case.
Here's how to do it in mockk (I highly recommend switching away from Mockito, mockk is just so much easier):
import TestSingleton.staticMethod1
import io.mockk.every
import io.mockk.just
import io.mockk.mockkStatic
import io.mockk.runs
import io.mockk.verify
import org.junit.jupiter.api.Test
internal object TestSingleton {
#JvmStatic
fun staticMethod1(str: String) {
println("+++ === +++ TestSingleton.staticMethod(), $str")
staticMethod2(str)
}
#JvmStatic
fun staticMethod2(str: String) {
println("+++ === +++ TestSingleton.staticMethod2(), $str")
}
}
class StackSign {
#Test
fun test_staticMethod() {
mockkStatic(TestSingleton::class)
every { TestSingleton.staticMethod2("test") } just runs
staticMethod1("test")
verify(exactly = 1) { TestSingleton.staticMethod2("test") }
}
}
BTW, add this to your build.gradle.kts
testImplementation("io.mockk:mockk:1.12.3")

InjectMocks doesn't work with Kotlin constructor arguments with default values

EDIT: I've created a ticket with mockito-kotlin here
I have a class defined like so:
package me.jpalacios.poc
class MyClass(
private val myDependency: MyDependency = MyDependency()
) {
fun run() {
myDependency.doSomething()
}
}
package me.jpalacios.poc
class MyDependency {
fun doSomething() {
println("I did something")
}
}
package me.jpalacios.poc
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.extension.ExtendWith
import org.mockito.InjectMocks
import org.mockito.Mock
import org.mockito.junit.jupiter.MockitoExtension
import org.mockito.kotlin.verify
#ExtendWith(MockitoExtension::class)
class MyClassTest {
#Mock
private lateinit var myDependency: MyDependency
#InjectMocks
private lateinit var myClass: MyClass
#Test
fun `Test InjectMocks`() {
myClass.run()
verify(myDependency).doSomething()
}
}
Looks like a test defined like so does not work because the mocks are not injected:
#ExtendWith(MockitoExtension::class)
class MyClassTest {
#Mock
private lateinit var dependency: MyDependency
#InjectMocks
private lateinit var underTest: MyClass
}
plugins {
kotlin("jvm") version "1.5.20"
}
group = "me.jpalacios"
version = "1.0-SNAPSHOT"
repositories {
mavenCentral()
}
dependencies {
implementation(kotlin("stdlib"))
testImplementation("org.junit.jupiter:junit-jupiter:5.7.2")
testImplementation("org.junit.jupiter:junit-jupiter-params:5.7.2")
testImplementation("org.assertj:assertj-core:3.20.2")
testImplementation("org.mockito.kotlin:mockito-kotlin:3.2.0")
testImplementation("org.mockito:mockito-junit-jupiter:3.11.2")
}
tasks{
jar {
duplicatesStrategy = DuplicatesStrategy.EXCLUDE
configurations["compileClasspath"].forEach { file: File ->
from(zipTree(file.absoluteFile))
}
}
compileKotlin {
kotlinOptions {
jvmTarget = "${JavaVersion.VERSION_11}"
}
}
test {
useJUnitPlatform()
}
}
Any thoughts as to why?
The output is:
I did something
Wanted but not invoked: myDependency.doSomething();
-> at me.jpalacios.poc.MyDependency.doSomething(MyDependency.kt:6) Actually, there were zero interactions with this mock.
Wanted but not invoked: myDependency.doSomething();
-> at me.jpalacios.poc.MyDependency.doSomething(MyDependency.kt:6) Actually, there were zero interactions with this mock.
at me.jpalacios.poc.MyDependency.doSomething(MyDependency.kt:6) at
me.jpalacios.poc.MyClassTest.Test InjectMocks(MyClassTest.kt:22) at
java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native
Method) at
java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at
java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
...
I really am no expert in Mockito, but adding default values for constructor arguments literally says that you don't need to be passed a dependency, so I wouldn't be surprised if Mockito doesn't try to inject mocks in this case. It really sees a no-arg constructor available, so wouldn't you want it to use it?
In any case, I'm not even sure you need this at all. Why not just instantiate the class under test yourself, and pass it the mock where you need it?
#ExtendWith(MockitoExtension::class)
class MyClassTest {
#Mock
private lateinit var myDependency: MyDependency
#Test
fun `Test InjectMocks`() {
MyClass(myDependency).run()
verify(myDependency).doSomething()
}
}
If you need to share such initialization among multiple tests, you can use a lazy property, or a #BeforeTest method to create it.

Jetpack compose - trying to get sound to play in viewmodel fun

I'm trying to play a sound outside of #Copmosable because of my application structure.
I have a validation routine which is in my viewmodel and based on the result I would like to trigger a sound but I cannot seem to get context working outside of #Composable
I get the following error in the MasterViewModel:
None of the following functions can be called with the arguments supplied.
create(Context!, Uri!) defined in android.media.MediaPlayer
create(Context!, Int) defined in android.media.MediaPlayer
Any pointers would be great thanks!!
package com.example.soundtest
import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.material.MaterialTheme
import androidx.compose.material.Surface
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.tooling.preview.Preview
import com.example.soundtest.ui.theme.SoundTestTheme
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
SoundTestTheme {
// A surface container using the 'background' color from the theme
Surface(color = MaterialTheme.colors.background) {
Greeting()
}
}
}
}
}
#Composable
fun Greeting(mastVM: MasterViewModel = MasterViewModel()) {
Text("Play a sound...")
mastVM.playSound()
}
and the MasterViewModel
package com.example.soundtest
import android.media.MediaPlayer
class MasterViewModel {
fun playSound() {
val mp: MediaPlayer = MediaPlayer.create(this, R.raw.correct)
}
}
I have correct.mp3 save under res->raw-correct.mp3
MasterViewModel Error
I got it working!!!
I just include
#Composable
fun Greeting(mastVM: MasterViewModel = MasterViewModel()) {
val context = LocalContext.current
Text("Play a sound...")
mastVM.playSound(context: context)
}
and then in the MasterViewModel like this
fun playSound(context: Context) {
val mp: MediaPlayer = MediaPlayer.create(context, R.raw.correct)
}

Quarkus Kotlin Inject failure

The #Inject annotation for a service, defined by "#ApplicationScope" fails to inject in Kotlin.
"kotlin.UninitializedPropertyAccessException: lateinit property greeter has not been initialized"
The explanation on similar question:
"This problem results as a combination of how Kotlin handles annotations and the lack of the a #Target on the ... annotation definition. Add #field: xxx"
Question is, how do I make this work for a service injection?
import com.amazonaws.services.lambda.runtime.Context
import com.amazonaws.services.lambda.runtime.RequestHandler
import javax.enterprise.context.ApplicationScoped
import javax.inject.Inject
class HelloRequest() {
var firstName: String? = null
var lastName: String? = null
}
#ApplicationScoped
open class HelloGreeter() {
open fun greet(firstName: String?, lastName: String?): String {
return "$firstName, $lastName"
}
}
class HelloLambda : RequestHandler<HelloRequest, String> {
#Inject
lateinit var greeter: HelloGreeter
override fun handleRequest(request: HelloRequest, context: Context): String {
return greeter.greet(request.firstName, request.lastName)
}
}
Similar questions:
"This problem results as a combination of how Kotlin handles annotations and the lack of the a #Target on the ... annotation definition. Add #field: xxx"
Error to inject some dependency with kotlin + quarkus
SmallRye Reactive Messaging's Emitter<>.send doesn't send in Kotlin via AMQP broker with Quarkus
I tested the modified, #field: xx with a standard rest service, and by qualifying it with #field: ApplicationScoped, it does now work.
The answer to my question above would be then that there is no runtime CDI when using the Quarkus Amazon Lambda extension.
import javax.enterprise.context.ApplicationScoped
import javax.inject.Inject
import javax.ws.rs.GET
import javax.ws.rs.Path
import javax.ws.rs.PathParam
import javax.ws.rs.Produces
import javax.ws.rs.core.MediaType
#ApplicationScoped
open class GreetingService2 {
fun greeting(name: String): String {
return "hello $name"
}
}
#Path("/")
class GreetingResource {
#Inject
#field: ApplicationScoped
lateinit var service: GreetingService2
#GET
#Produces(MediaType.TEXT_PLAIN)
#Path("/greeting/{name}")
fun greeting(#PathParam("name") name: String): String {
return service.greeting(name)
}
#GET
#Produces(MediaType.TEXT_PLAIN)
fun hello(): String {
return "hello"
}
}

How to migrate Kotlin from 1.2 to Kotlin 1.3.0 then using async, UI, and bg in a presenter function

I am using MVP pattern on a Kotlin Project. I have a Presenter class:
import com.google.gson.Gson
import kotlinx.coroutines.experimental.android.UI
import kotlinx.coroutines.experimental.async
import org.jetbrains.anko.coroutines.experimental.bg
class TeamsPresenter(private val view: TeamsView,
private val apiRepository: ApiRepository,
private val gson: Gson
) {
fun getTeamList(league: String?) {
view.showLoading()
async(UI){
val data = bg {
gson.fromJson(apiRepository
.doRequest(TheSportDBApi.getTeams(league)),
TeamResponse::class.java
)
}
view.showTeamList(data.await().teams)
view.hideLoading()
}
}
}
this presenter class working fine on Kotlin 1.2.71, but I can't get it working on Kotlin 1.3.0.
I updated Kotlin version in project's build.gradle, removed "experimental coroutines" and added kotlin coroutine core dependency:
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.0.0'
and this is my current code:
import com.google.gson.Gson
class TeamsPresenter(private val view: TeamsView,
private val apiRepository: ApiRepository,
private val gson: Gson
) {
fun getTeamList(league: String?) {
view.showLoading()
async(UI){
val data = bg {
gson.fromJson(apiRepository
.doRequest(TheSportDBApi.getTeams(league)),
TeamResponse::class.java
)
}
view.showTeamList(data.await().teams)
view.hideLoading()
}
}
}
Error mainly on async, UI, and bg function:
unresolved reference: async
unresolved reference: UI
unresolved reference: bg
How can I get this to work on Kotlin 1.3.0? for any help, thanks in advance.
you must use GlobalScope.launch instead of launch ,GlobalScope.async instead of async
Dispatcher.main instead of UI
coroutineBasics
Your code has several layers of problems:
You're using async, but you don't await on it. You should be using launch instead.
You're using the pre-coroutines facility of bg, equivalent to async
You immediately await on bg, which means you should be using withContext(Default) instead
(new with Kotlin 1.3) You aren't applying structured concurrency
This is how your code should look in Kotlin 1.3:
fun CoroutineScope.getTeamList(league: String?) {
view.showLoading()
this.launch {
val data = withContext(Dispatchers.IO) {
gson.fromJson(apiRepository.doRequest(TheSportDBApi.getTeams(league)),
TeamResponse::class.java
)
}
view.showTeamList(data.teams)
view.hideLoading()
}
}
You should call your function with the coroutine scope appropriate to your situation. A typical approach is tying it to your activity:
class MyActivity : AppCompatActivity(), CoroutineScope {
lateinit var masterJob: Job
override val coroutineContext: CoroutineContext
get() = Dispatchers.Main + masterJob
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
masterJob = Job()
}
override fun onDestroy() {
super.onDestroy()
masterJob.cancel()
}
}
Also add
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.0.1'
Here is a fix ( I don't know if it is the best way to do it) :
import com.google.gson.Gson
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.async
import kotlinx.coroutines.launch
class TeamsPresenter(private val view: TeamsView,
private val apiRepository: ApiRepository,
private val gson: Gson
) {
fun getTeamList(league: String?) {
view.showLoading()
CoroutineScope(Dispatchers.Main).launch {
val data = async {
gson.fromJson(apiRepository
.doRequest(TheSportDBApi.getTeams(league)),
TeamResponse::class.java
)
}
view.showTeamList(data.await().teams)
view.hideLoading()
}
}
}