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"
}
}
Related
When trying to register a type adapter, where list is java.util.List
GsonBuilder().registerTypeAdapter(object : TypeToken<List<MyObject>>() {}.type, MyObjectsTypeAdapter())
I get the following warning:
This class shouldn't be used in Kotlin. Use kotlin.collections.List or
kotlin.collections
My type adapter uses a kotlin.collections.list.
class MyObjectsTypeAdapter: TypeAdapter<List<MyObject>>() {
...
}
However if I don't use java.util.List in my type adapter, gson does not match the type correctly.
Am I doing something else wrong when registering my type adapter?
Below is small demo showing Retrofit usage with Gson. A few important points:
To use Gson as converter you have to add the com.squareup.retrofit2:converter-gson dependency and add the converter factory with addConverterFactory(GsonConverterFactory.create()); see Retrofit documentation
The custom TypeAdapterFactory in the example below is not needed; it just shows how you could customize the Gson instance
import com.google.gson.*
import com.google.gson.reflect.TypeToken
import com.google.gson.stream.*
import io.ktor.server.application.*
import io.ktor.server.engine.*
import io.ktor.server.netty.*
import io.ktor.server.response.*
import io.ktor.server.routing.*
import retrofit2.*
import retrofit2.converter.gson.GsonConverterFactory
import retrofit2.http.GET
data class MyObject(
val message: String
)
interface DemoService {
#GET("demo")
fun demo(): Call<List<MyObject>>
}
fun main() {
val port = 8080
// Start a demo server returning the JSON response
embeddedServer(Netty, port) {
routing {
get("/demo") {
call.respondText("[{\"message\":\"Hello from server\"}]")
}
}
}.start(wait = false)
val gson = GsonBuilder()
.registerTypeAdapterFactory(object: TypeAdapterFactory {
override fun <T> create(gson: Gson, type: TypeToken<T>): TypeAdapter<T>? {
if (type.rawType != MyObject::class.java) {
return null
}
#Suppress("UNCHECKED_CAST") // safe due to type check at beginning
val delegate = gson.getDelegateAdapter(this, type) as TypeAdapter<MyObject>
val adapter = object: TypeAdapter<MyObject>() {
override fun write(writer: JsonWriter, value: MyObject?) {
return delegate.write(writer, value)
}
override fun read(reader: JsonReader): MyObject? {
val value: MyObject? = delegate.read(reader)
return value?.copy(message = "custom-prefix: ${value.message}")
}
}
#Suppress("UNCHECKED_CAST") // safe due to type check at beginning
return adapter as TypeAdapter<T>
}
})
.create()
val retrofit = Retrofit.Builder()
.baseUrl("http://localhost:$port/")
.addConverterFactory(GsonConverterFactory.create(gson))
.build()
val service = retrofit.create(DemoService::class.java)
val response = service.demo().execute()
println(response.body())
}
The following dependencies were used:
// For demo server
implementation("org.slf4j:slf4j-simple:2.0.0")
implementation("io.ktor:ktor-server-core:2.1.0")
implementation("io.ktor:ktor-server-netty:2.1.0")
implementation("com.squareup.retrofit2:retrofit:2.9.0")
implementation("com.squareup.retrofit2:converter-gson:2.9.0")
implementation("com.google.code.gson:gson:2.9.1")
I expected this to return a list of Money objects, but it returns LinkedHashMap instead, which is the generic fallback type I guess. It seems the reified type parameter is not being propagated, even though it should.
Here is a self-contained example (dependencies: assertj, junit, jackson-databind, kotlin-1.4.32):
import com.fasterxml.jackson.annotation.JsonProperty
import com.fasterxml.jackson.databind.ObjectMapper
import com.fasterxml.jackson.datatype.jdk8.Jdk8Module
import com.fasterxml.jackson.module.kotlin.KotlinModule
import com.fasterxml.jackson.module.kotlin.convertValue
import org.assertj.core.api.Assertions.assertThat
import org.junit.jupiter.api.Test
class MapperTest {
val JSON_MAPPER = ObjectMapper().apply {
registerModule(KotlinModule())
registerModule(Jdk8Module())
}
data class Money(#JsonProperty("Currency") val currency: String?)
#Test
fun testMapper() {
val map = listOf(mapOf("Currency" to "EUR"))
assertThat(fromMap(map)[0]).isInstanceOf(Money::class.java) // passes
assertThat(fromMapReified<Money>(map)[0]).isInstanceOf(Money::class.java) // fails
}
private fun fromMap(map: List<Map<String, String>>) : List<Money> {
return JSON_MAPPER.convertValue(map)
}
private inline fun <reified T> fromMapReified(map: List<Map<String, String>>) : List<T> {
return JSON_MAPPER.convertValue(map)
}
}
Error with Kotlin conversion from java, this project is testing Github Api and displaying data in recyclerview.I get the a compile error which i cannot workout for Dagger2, it worked in Java but when converting to Kotlin i get a compile error at runtime.
It seems to be with injecting an api method into the view model
i have tried following the error then cleaning and rebuilding the app
I have also tried invalidating caches and restarting but seems there is an error with the conversion into Kotlin from Java. Any help would be appreciated.
Here is my class:
class RepoRepository {
private lateinit var repoService: GithubRepos
#Inject
fun RepoRepository(repoService: GithubRepos) {
this.repoService = repoService
}
fun getRepositories(): Single<List<Repo>> {
return repoService.getRepos()
}
fun getSingleRepo(owner: String, name: String): Single<Repo> {
return repoService.getSingleRepo(owner, name)
}
}
My component class:
#Singleton
#Component(modules = arrayOf(NetworkModule::class))
interface AppComponent {
/**
* inject required dependencies into MainActivityListViewModel
*/
fun inject(mainActivityListViewModel: MainActivityListViewModel)
#Component.Builder
interface Builder {
fun build(): AppComponent
fun networkModule(networkModule: NetworkModule): Builder
}
}
And my ViewModel:
class MainActivityListViewModel : BaseViewModel() {
private lateinit var repoRepository: RepoRepository
private var disposable: CompositeDisposable? = null
private val repos = MutableLiveData<List<Repo>>()
private val repoLoadError = MutableLiveData<Boolean>()
private val loading = MutableLiveData<Boolean>()
#Inject
fun ListViewModel(repoRepository: RepoRepository) {
this.repoRepository = repoRepository
disposable = CompositeDisposable()
fetchRepos()
}
fun getRepos(): LiveData<List<Repo>> {
return repos
}
fun getError(): LiveData<Boolean> {
return repoLoadError
}
fun getLoading(): LiveData<Boolean> {
return loading
}
private fun fetchRepos() {
loading.value = true
disposable?.add(repoRepository.getRepositories()
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribeWith(object :
DisposableSingleObserver<List<Repo>>() {
override fun onSuccess(value: List<Repo>) {
repoLoadError.value = false
repos.value = value
loading.value = false
}
override fun onError(e: Throwable) {
repoLoadError.value = true
loading.value = false
}
}))
}
override fun onCleared() {
super.onCleared()
if (disposable != null) {
disposable!!.clear()
disposable = null
}
}
}
this is error i am getting:
[Dagger/MissingBinding] repos.network.RepoRepository cannot be
provided without an #Inject constructor or an #Provides-annotated
method. This type supports members injection but cannot be
implicitly provided.
public abstract repos.network.RepoRepository
repoRepository();
^
repos.network.RepoRepository is provided at
components.AppComponent.repoRepository() e: repos/di/components/AppComponent.java:19: error:
[Dagger/MissingBinding] repos.network.RepoRepository cannot be
provided without an #Inject constructor or an #Provides- annotated
method. This type supports members injection but cannot be
implicitly provided.
^
repos.network.RepoRepository is injected at
repos.viewmodels.MainActivityListViewModel.ListViewModel(repoRepository)
repos.viewmodels.MainActivityListViewModel is injected at
repos.di.components.AppComponent.inject(repos.viewmodels.MainActivityListViewModel)
Your error clearly says:
[Dagger/MissingBinding] repos.network.RepoRepository cannot be provided without an #Inject constructor or an #Provides-annotated method.
You didn't define constructor for your RepoRepository class.
It should look something like this:
class RepoRepository #Inject constructor(private val repoService: GithubRepos) {//the rest of your code here}
This goes for your viewmodel class as well.
If you are using android ViewModel architecture component i suggest you read this article which explains how to use it with Dagger2.
Hope this helps.
I'm trying MVVM, Dagger2, Retrofit and Coroutine. Now I have the problem that I can successfully inject a ProfileService into my activity, but not into my repository. I get a profileService lateinit property has not been initialized
//MainActivity
#Inject
lateinit var profileService: ProfileService //only for testing
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val getTheExpectedResult = profileService.hasValidName("asdffff") //true
profileViewModel.createProfile("ku") //throw the not initialized
}
The viewmodel calls the repository.
// profile repository
private fun getProfileRepository(userId: String = "", apiKey: String = ""): ProfileRepository {
return ProfileRepository(ApiFactory.getApi(userId, apiKey))
}
fun createProfile(name: String) {
scope.launch {
try {
val profile = getProfileRepository().createProfile(name)
profileLiveData.postValue(profile)
}
//...
In the repository I inject the profileService
class ProfileRepository(private val api: NIPApiInterface) {
#Inject
lateinit var profileService: ProfileService
suspend fun createProfile(name: String): ProfileResponse? {
if (!profileService.hasValidName(name)) { //throw the not initialized
//...
My unspectacular ProfileService
class ProfileService #Inject constructor() {
fun hasValidName(name: String): Boolean {
return name.length > 3
}
}
So that I don't post too much code, the following info. In my Application I seem to initialize everything correctly, because the Activity can access the ProfileService. Here are my Dagger configurations:
//AppComponent
#Singleton
#Component(
modules = [
AndroidSupportInjectionModule::class, AppModule::class, ActivityBuilder::class
]
)
interface AppComponent : AndroidInjector<NIPApplication> {
#Component.Factory
interface Factory {
fun create(#BindsInstance application: NIPApplication): AppComponent
}
}
The AppModule
#Module
class AppModule {
#Provides
#Singleton
fun provideContext(app: NIPApplication): Context = app
}
If you need more code, please send a comment.
Dagger won't inject dependencies unless it creates an instance of a class that needs injection or is explicitly asked to. Since the ProfileRepository object is created by you, it won't get its dependencies injected by Dagger.
The best way to solve this is to let Dagger create ProfileRepository objects by #Inject-annotated constructor (or using #Provides-annotated method in a Dagger module (provider), however this seems quite redundant in this particular case):
class ProfileRepository #Inject constructor(
private val api: NIPApiInterface,
private val profileService
)
Note that now Dagger will want to inject an NIPApiInterface object as well, so you have to create a provider for that or remove it from the constructor and pass it in some other way.
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()
}
}
}