dagger #IntoSet not work in kotlin object? - kotlin

I used example in https://google.github.io/dagger/multibindings
Module A
#Module
object MyModuleA {
#Provides
#IntoSet
fun provideOneString(): String {
return "ABC"
}
}
Module B
#Module
object MyModuleB {
#Provides
#ElementsIntoSet
fun provideSomeStrings(): Set<String> {
return HashSet<String>(Arrays.asList("DEF", "GHI"))
}
}
component
#Component(modules = [ MyModuleA::class, MyModuleB::class])
interface MyComponent {
fun strings(): Set<String>
}
test
#Test
fun testMyComponent() {
val myComponent = DaggerMyComponent.builder().build()
println("${myComponent.strings()}")
}
It show error with MyModuleA must be set, but changing module from object to class is work fine.
#Module
class MyModuleA {
#Provides
#IntoSet
fun provideOneString(): String {
return "ABC"
}
}
#Module
class MyModuleB {
#Provides
#ElementsIntoSet
fun provideSomeStrings(): Set<String> {
return HashSet<String>(Arrays.asList("DEF", "GHI"))
}
}
#IntoSet annotation is not work in kotlin object?

I ran into the same problem and this answer solved it for me.
Dagger 2 multibindings with Kotlin
In short, you need to use Set<#JvmSuppressWildcards String> intead, because of the way that kotlin handles type variance in generics.

Related

Mockk #OverrideMockKs not working with Kotest

I've using Kotest recently and I hadn't had any issues, but recently I was trying some annotations for dependency injection so to simplify the problem I created some basic classes with some methods that just print some messages, just for the sake of learning how to use Kotest and Mockk, but during the testing, I ran with the exception that the variable hasn't been initialized when trying to run the test.
These are my classes
class DefaultClass : AbstractClass() {
private val anotherClass: AnotherClass = AnotherClass()
fun testMethod(value: String): String {
val normalizeValue = value.trim().lowercase().replace(Regex("[^ A-Za-z\\d]*"), "")
return runBlocking {
anotherClass.someOtherMethod()
callsProtectedMethod(normalizeValue)
}
}
private suspend fun callsProtectedMethod(value: String) = coroutineScope {
println("Original method")
returnDefaultString(value)
}
}
AnotherClass
class AnotherClass {
fun someOtherMethod(): Unit {
println("SomeOtherMethod original")
}
}
Test
class DefaultClassTest : FunSpec({
context("Testing DefaultClass") {
#MockK
lateinit var anotherClass: AnotherClass
#OverrideMockKs
lateinit var defaultClass: DefaultClass
beforeContainer {
MockKAnnotations.init(this)
}
test("testing mocks") {
defaultClass.testMethod("some method")
}
}
I've changed the initialization to beforeTest, taken it out of the context, and also use beforeContainer, beforeTest, beforeSpec, but none of these work... every time I still get lateinit property defaultClass has not been initialized
So, I recreated the same test using JUnit and I don't have this issue.
class DefaultClassJUnitTest {
companion object {
#MockK
lateinit var anotherClass: AnotherClass
#OverrideMockKs
lateinit var defaultClass: DefaultClass
#BeforeAll
#JvmStatic
fun setup() {
MockKAnnotations.init(this)
}
}
#Test
fun `Testing with JUnit`() {
every { anotherClass.someOtherMethod() } answers {
println("Mocking another class")
}
val value = defaultClass.testMethod("some method")
}
}
So I'm pretty sure that I'm doing something wrong when using Kotest. I hope anyone might help me, thanks...
I think MockK is probably not looking for variables defined within function scopes. If you want to use the annotations, you likely have to move them to the companion object, like this:
class DefaultClassTest : FunSpec({
context("Testing DefaultClass") {
beforeContainer {
MockKAnnotations.init(this)
}
test("testing mocks") {
defaultClass.testMethod("some method")
}
}
}) {
companion object {
#MockK
lateinit var anotherClass: AnotherClass
#OverrideMockKs
lateinit var defaultClass: DefaultClass
}
}

#subcomponent.factory method is missing parameters for required modules or subcomponents

I am injecting Presenter to BookDashboard(Activity) and BookDashboardPresenter class require an MvpView interface in it's constructor .
When i run
AppComponent
#Component(
modules = [
AndroidInjectionModule::class,
ActivityBuilder::class
]
)
#Singleton
interface AppComponent : AndroidInjector<App> {
#Component.Builder
interface Builder {
fun addContext(#BindsInstance context: Context): Builder
fun addBookEngine(#BindsInstance bookEngineModule: BookEngineModule) :Builder
fun build(): AppComponent
}
}
ActivityBuilder.kt
#Module
abstract class ActivityBuilder {
#ContributesAndroidInjector(modules = {BookEngineModule.class})
public abstract BookDashboard bindBookDashboard();
}
BookEngineModule.kt
#Module
class BookEngineModule(val mvpView: BookDashboardContract.MvpView){
#Provides
fun providePresenter():BookDashboardContract.Presenter{
return BookDashboardPresenter(mvpView)
}
}
BookDashboardContract
interface BookDashboardContract {
interface MvpView{
fun displayBooks()
fun showProgress()
fun hideProgress()
}
interface Presenter{
fun fetchedBooks()
}
}
BookDashboardPresenter.kt
class BookDashboardPresenter #Inject constructor(val viewContract:BookDashboardContract.MvpView) : BookDashboardContract.Presenter{
val bookInteractor = BookInteractor(this)
override fun fetchedBooks() {
bookInteractor.fetchDataFromServer()
viewContract.displayBooks()
}
}
BookDashboard -> Activity
class BookDashboard : DaggerAppCompatActivity(),BookDashboardContract.MvpView{
#Inject
lateinit var presenter: BookDashboardContract.Presenter
override fun onCreate(savedInstanceState: Bundle?) {
DaggerAppComponent.builder().addContext(this).
addBookEngine(BookEngineModule(this)).build()
super.onCreate(savedInstanceState)
///presenter.fetchedBooks()
}
override fun displayBooks() {
Toast.makeText(this,"Books Displayed",Toast.LENGTH_LONG).show()
}
override fun showProgress() {}
override fun hideProgress() {}
}
But when I build the project I am having below error
ActivityBuilder_BindBookDashboard.java:24: error: #Subcomponent.Factory method is missing parameters for required modules or subcomponents: [quiz.mania.trivia.mcq.question.di.BookEngineModule]
interface Factory extends AndroidInjector.Factory<BookDashboard> {}
DaggerClass
#Module(subcomponents = ActivityBuilder_BindBookDashboard.BookDashboardSubcomponent.class)
public abstract class ActivityBuilder_BindBookDashboard {
private ActivityBuilder_BindBookDashboard() {}
#Binds
#IntoMap
#ClassKey(BookDashboard.class)
abstract AndroidInjector.Factory<?> bindAndroidInjectorFactory(
BookDashboardSubcomponent.Factory builder);
#Subcomponent(modules = BookEngineModule.class)
public interface BookDashboardSubcomponent extends AndroidInjector<BookDashboard> {
#Subcomponent.Factory
interface Factory extends AndroidInjector.Factory<BookDashboard> {}
}
}
above class can't resolve BookEngineModule and BookDashboard
What am i missing ?

How to fix "[Dagger/MissingBinding]" in kotlin?

I'm trying to inject LatestChart in AppComponent and sloving issue with [Dagger/MissingBinding] LatestChart cannot be provided without an #Inject constructor or an #Provides-annotated method...
Thanks in advance for your help
LatestChart.kt
class LatestChart {
#Inject
lateinit var context: Context
#Inject
lateinit var formatter: YearValueFormatter
lateinit var chart: LineChart
init {
App.appComponent.inject(this)
}
fun initChart(chart: LineChart) {
this.chart = chart
***
}
fun addEntry(value: Float, date: Float) {
***
}
}
private fun createSet(): LineDataSet {
***
}
return set
}
}
And AppComponent.kt
#Component(modules = arrayOf(AppModule::class, RestModule::class, MvpModule::class, ChartModule::class))
#Singleton
interface AppComponent {
***
fun inject(chart: LatestChart)
}
Compilation error:
***AppComponent.java:8: error: [Dagger/MissingBinding] sn0w.test.crypto.chart.LatestChart cannot be provided without an #Inject constructor or an #Provides-annotated method. This type supports members injection but cannot be implicitly provided.
public abstract interface AppComponent {
^
A binding with matching key exists in component: sn0w.test.crypto.di.AppComponent
sn0w.test.crypto.chart.LatestChart is injected at
sn0w.test.crypto.activities.ChartActivity.latestChart
sn0w.test.crypto.activities.ChartActivity is injected at
sn0w.test.crypto.di.AppComponent.inject(sn0w.test.crypto.activities.ChartActivity)
The way you are currently trying to inject dependencies into the LatestChart is how you satisfy the dependencies in objects that won't be created by Dagger (e.g. activities). If you create the LatestChart object by yourself (just val latestCharts = LatestCharts()), you'll see that context and formatter are actually injected into this newly created instance.
However if you want Dagger to inject a LatestChart object into another (into ChartActivity I assume based on the compilation error), you have to let Dagger know how to create its instances. There are 2 ways to achieve that:
1. Annotate the LatestChart constructor with #Inject
class LatestChart #Inject constructor(
private val context: Context,
private val formatter: YearValueFormatter
) {
lateinit var chart: LineChart
fun initChart(chart: LineChart) { ... }
fun addEntry(value: Float, date: Float) { ... }
private fun createSet(): LineDataSet { ... }
}
2. Create a provider method in one of Dagger modules
class LatestChart(private val context: Context, private val formatter: YearValueFormatter) {
lateinit var chart: LineChart
fun initChart(chart: LineChart) { ... }
fun addEntry(value: Float, date: Float) { ... }
private fun createSet(): LineDataSet { ... }
}
#Module
class LatestChartModule {
#Module
companion object {
#JvmStatic
#Provides
fun provideLatestChart(context: Context, formatter: YearValueFormatter): LatestChart =
LatestChart(context, formatter)
}
}

#Provides and #Binds methods in same class Kotlin

After dagger 2.11 we can use a #Binds annotation and mark our Module as abstract in this case which is more efficient than a concrete.
If my Module has both #Provides and #Binds methods, I have two options :
Simplest would be to mark your #Provides instance methods as static.
If it is necessary to keep them as instance methods, then you can
split your module into two and extract out all the #Binds methods
into an abstract Module.
The second option works fine in Java and Kotlin but the first option works fine in Java but I don't know how to implement the same in Kotlin. If I move #Provides method to Companion object it throw Error:(30, 1) error: #Provides methods can only be present within a #Module or #ProducerModule.
How can do this in Kotlin.
Second Option:(Working)
ApplicationModule.kt
#Module(includes = [ApplicationModule.Declarations::class])
abstract class ApplicationModule {
#Module
internal interface Declarations {
#Binds
fun bindContext(application: Application): Context
}
#Provides
#Singleton
fun provideMvpStarterService(): MvpStarterService {
return MvpStarterServiceFactory.makeStarterService()
}
}
First Option:(Not working)
ApplicationModule.kt
#Module
abstract class ApplicationModule {
//expose Application as an injectable context
#Binds
internal abstract fun bindContext(application: Application): Context
companion object {
#JvmStatic
#Provides
#Singleton
fun provideMvpStarterService(): MvpStarterService {
return MvpStarterServiceFactory.makeStarterService()
}
}
}
Generated Java file for first option:
#kotlin.Metadata(mv = {1, 1, 9}, bv = {1, 0, 2}, k = 1, d1 = {"\u0000\u001a\n\u0002\u0018\u0002\n\u0002\u0010\u0000\n\u0002\b\u0002\n\u0002\u0018\u0002\n\u0000\n\u0002\u0018\u0002\n\u0002\b\u0003\b\'\u0018\u0000 \b2\u00020\u0001:\u0001\bB\u0005\u00a2\u0006\u0002\u0010\u0002J\u0015\u0010\u0003\u001a\u00020\u00042\u0006\u0010\u0005\u001a\u00020\u0006H!\u00a2\u0006\u0002\b\u0007\u00a8\u0006\t"}, d2 = {"Lio/mywebsie/di/ApplicationModule;", "", "()V", "bindContext", "Landroid/content/Context;", "application", "Landroid/app/Application;", "bindContext$app_debug", "Companion", "app_debug"})
#dagger.Module()
public abstract class ApplicationModule {
public static final io.mywebsie.di.ApplicationModule.Companion Companion = null;
#org.jetbrains.annotations.NotNull()
#dagger.Binds()
public abstract android.content.Context bindContext$app_debug(#org.jetbrains.annotations.NotNull()
android.app.Application application);
public ApplicationModule() {
super();
}
#org.jetbrains.annotations.NotNull()
#javax.inject.Singleton()
#dagger.Provides()
public static final io.mywebsie.data.remote.MvpStarterService provideMvpStarterService() {
return null;
}
#kotlin.Metadata(mv = {1, 1, 9}, bv = {1, 0, 2}, k = 1, d1 = {"\u0000\u0012\n\u0002\u0018\u0002\n\u0002\u0010\u0000\n\u0002\b\u0002\n\u0002\u0018\u0002\n\u0000\b\u0086\u0003\u0018\u00002\u00020\u0001B\u0007\b\u0002\u00a2\u0006\u0002\u0010\u0002J\b\u0010\u0003\u001a\u00020\u0004H\u0007\u00a8\u0006\u0005"}, d2 = {"Lio/mywebsie/di/ApplicationModule$Companion;", "", "()V", "provideMvpStarterService", "Lio/mywebsie/data/remote/MvpStarterService;", "app_debug"})
public static final class Companion {
#org.jetbrains.annotations.NotNull()
#javax.inject.Singleton()
#dagger.Provides()
public final io.mywebsie.data.remote.MvpStarterService provideMvpStarterService() {
return null;
}
private Companion() {
super();
}
}
}
Update:
Thanks to #David Medenjak the link you provided in comment made everything clear I came across two ways to achieve the first option.
Updated code:
First Option:(Working)
ApplicationModule.kt
#Module(includes = [ApplicationModule.AModule::class])
abstract class ApplicationModule {
#Binds
abstract fun bindContext(application: Application): Context
#Module
object AModule {
#JvmStatic
#Provides
#Singleton
fun provideMvpStarterService(): MvpStarterService {
return MvpStarterServiceFactory.makeStarterService()
}
}
}
or
#Module
abstract class ApplicationModule {
#Binds
abstract fun bindContext(application: Application): Context
#Module
companion object {
#Provides
#Singleton
fun provideMvpStarterService(): MvpStarterService {
return MvpStarterServiceFactory.makeStarterService()
}
}
}
Both works just fine but for some reason the first option does not look appealing to me so I prefer the second option.
Here's an example code to demonstrate how to use Binds and Provides annotated methods in a single Kotlin class:
#Module
abstract class MessagesPresentationModule {
#Module
companion object {
const val MESSAGES = 0x00
#JvmStatic
#Provides
fun provideRecyclerAdapter(
itemComparator: DisplayItemComperator,
factoryMap: Map<Int, ViewHolderFactory>,
binderMap: Map<Int, ViewHolderBinder>,
androidPreconditions: AndroidPreconditions
): RecyclerViewAdapter {
return RecyclerViewAdapter(
itemComperator = itemComparator,
viewHolderFactoryMap = factoryMap,
viewBinderFactoryMap = binderMap,
androidPreconditions = androidPreconditions
)
}
}
#Binds
#IntoMap
#IntKey(MESSAGES)
internal abstract fun provideMessagesViewModelFactory(factory: MessagesViewHolder.MessageViewHolderFactory): ViewHolderFactory
#Binds
#IntoMap
#IntKey(MESSAGES)
internal abstract fun provideMessagesViewHolderBinder(binder: MessagesViewHolder.MessagesViewHolderBinder): ViewHolderBinder
}

Replacing SAM-constructor with lambda with covariant type

I have got the following Java interfaces:
interface Action1<T> {
void call(T t);
}
interface Test<T> {
void test(Action1<? super T> action)
}
And the following Kotlin class:
interface A {
fun go()
}
abstract class Main {
abstract fun a(): Test<out A>
fun main() {
a().test(Action1 { it.go() })
a().test { it.go() }
}
}
Now in the function main, the first statement compiles, but IntelliJ gives a warning that the SAM-constructor can be replaced with a lambda.
This would result in the second statement.
However, this second statement does not compile, because it has type Any?, not A. Removing the out modifier makes it compile again.
Why does this happen?
The use case of this is when the implementing class of Main needs to return Test<B> for the function a(), where B implements A:
class B : A {
override fun go() {
TODO()
}
}
class MainImp : Main() {
override fun a(): Test<out A> {
val value: Test<B> = object : Test<B> {
override fun test(action: Action1<in B>?) {
TODO()
}
};
return value
}
}
It is a compiler bug. You can track it here: https://youtrack.jetbrains.com/issue/KT-12238.