Sakiboy
Sakiboy

Reputation: 7459

Kotlin Kodein NotFoundException: No binding found for bind<String>() with ?<Fragment>()

I have a very simple set up. But I am unable to perform any networking within my the fragment that I am using Kodein in.

The fragment inflates and handles all interaction but the networking layer dependency is somehow missing because I receive: NotFoundException: No binding found for bind<String>() with ?<Fragment>() every time I hit the button that validates the username/password on this LoginFragmnet.

I don’t understand this error because my Fragment class doesn’t have any String in it’s args/constructor? Or even depend on a String.

My Application class:

...
override val kodein = Kodein.lazy {
    import(androidXModule(this@MyApplication))
    bind() from singleton { MyLoginApi(instance(), instance()) }
    bind<LoginDataSource>() with singleton { LoginService(instance()) }
    bind<LoginRepository>() with singleton { LoginRepositoryImpl(instance()) }
}
...

My API

interface MyLoginApi {

    @POST("Account/Login")
    @FormUrlEncoded
    fun login(
        @Field("username") username: String,
        @Field("password") password: String,
        @Field("sessionType") sessionType: String
    ): Call<BaseApiResponse<Login>>

    @GET("Account/Logout")
    fun logout(
        @Query("token") token: String
    ): Call<BaseApiResponse<Any>>

    companion object {
        operator fun invoke(
            baseUrl: String,
            cache: Cache
        ): MyLoginApi {

            val okHttpClient = OkHttpClient.Builder()
                .cache(cache)
                .readTimeout(60, TimeUnit.SECONDS)
                .connectTimeout(60, TimeUnit.SECONDS)
                .build()

            return Retrofit.Builder()
                .client(okHttpClient)
                .baseUrl(baseUrl)
                .addConverterFactory(JacksonConverterFactory.create())
                .build()
                .create(MyLoginApi::class.java)
        }
    }
}

LoginService (The Datasource)

class LoginService(private val api: MyLoginApi) : LoginDataSource {
    private val bus = EventBus.getDefault()

    init {
        bus.register(this)
    }

    override fun requestLogin(username: String, password: String) {
        val call = api.login(username, password, "mobile")
        call.enqueue { result ->
            when (result) {
                is Result.Success -> {

                    result.response.body()?.let {
                        bus.post(LoginResponse(it, username))
                    } ?: bus.post(LoginResponse(IOException("System Error!")))

                }
                is Result.Failure -> {
                    bus.post(LoginResponse(IOException(result.error)))
                }
            }
        }
}

An Abstract Fragment class:

abstract class InjectionFragment : Fragment(), KodeinAware {

    final override val kodeinContext = kcontext<Fragment>(this)
    final override val kodein: Kodein by kodein()

    final override val kodeinTrigger: KodeinTrigger?
        get() = if (BuildConfig.DEBUG) KodeinTrigger() else super.kodeinTrigger

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        kodeinTrigger?.trigger()
    }
}

I know this scenario is very trivial, but I’m trying to do this as a test before integrating in the rest of the project. The Fragment displays, but for some reason once I try to do a data request it crashes with an error saying:

org.kodein.di.Kodein$NotFoundException: No binding found for bind<String>() with ?<Fragment>().? { ? }

module ⁣androidModule {
bind<String>(tag = "packageResourcePath") with contexted<Context>().provider { String }
bind<String>(tag = "packageCodePath") with contexted<Context>().provider { String }
bind<String>(tag = "packageName") with contexted<Context>().provider { String }
}

Update: When single stepping through my Fragment it the debugger says that the Repository is "Lazy value not initialized yet.”

Upvotes: 1

Views: 3479

Answers (2)

Sakiboy
Sakiboy

Reputation: 7459

I ended up going with Hilt it's build upon Dagger and easy to use and google is doing a good job maintaining it. I'd suggest others to use Hilt as well.

Upvotes: -1

Berni
Berni

Reputation: 568

You should bind the String "class" with an instance of itself inside the kodein.lazy initialization before you bind classes that use instances of String

  override val kodein = Kodein.lazy {
  import(androidXModule(this@MyApplication))

  bind() from singleton {String()}     //   --->  Add THIS line

  bind() from singleton { MyLoginApi(instance(), instance()) }
  bind<LoginDataSource>() with singleton { LoginService(instance()) }
  bind<LoginRepository>() with singleton { LoginRepositoryImpl(instance()) }

  }
    ...

You can do that because in this case the constructor of the class String is public, but do not take it as final solution since this is not possible with other data types like Integer, as its constructor is private.

The real problem may lay in this binding

bind() from singleton { MyLoginApi(instance(), instance()) }

The instances inside MyLoginApi are instances of strings, and since you do not have an implementation of MyLoginApi interface, the Kodein library directly tries to find where those instances are defined. So better create MyLoginApiImpl and then substitute:

 bind() from singleton { MyLoginApi(instance(), instance()) }

with

bind<MyLoginApi>() with singleton { MyLoginApiImpl(instance())}

Upvotes: 1

Related Questions