Help solve the problem!(((I have 3 modules for DI. There is a retrofit object in natworkModule, all viewModels in viewModelModule, and all requests to the server in respositoryModule. I did everything according to the documentation, but I cannot find this error in Google. Thank you in advance!!! Sorry for my english!)
class App : Application(){
override fun onCreate() {
super.onCreate()
startKoin(this, listOf(natworkModule, viewModelModule,repositoryModule))
}
}
var natworkModule = module {
single { createOkHttpClient() }
single { createApiService<ApiService>(get () ,getProperty(SERVER_URL))
}
}
const val SERVER_URL = "https://api.github.com/"
fun createOkHttpClient() : OkHttpClient{
val httpLoggingInterceptor = HttpLoggingInterceptor()
httpLoggingInterceptor.level = HttpLoggingInterceptor.Level.BASIC
return OkHttpClient.Builder()
.connectTimeout(60L, TimeUnit.SECONDS)
.readTimeout(60L, TimeUnit.SECONDS)
.addInterceptor(httpLoggingInterceptor).build()
}
inline fun <reified T> createApiService(okHttpClient: OkHttpClient, url: String): T {
val retrofit = Retrofit.Builder()
.baseUrl(url)
.client(okHttpClient)
.addConverterFactory(GsonConverterFactory.create())
.addCallAdapterFactory(LiveDataCallAdapterFactory()).build()
return retrofit.create(T::class.java)
}
var repositoryModule = module {
factory<TestRepository> {
TestRepositoryImpl(get())
}
}
var viewModelModule = module {
viewModel {
TestViewModel(get())
}
}
Problem was in this constant value ->
SERVER_URL = "https://api.github.com/"
Koin could not find it. Therefore there was an exception. Thanks to all!!!
Related
I'm new to the Koin so hopefully someone will be able to point out the direction of the issue I'm encountering.
I've an Interface class:
interface UserApi {
#POST("/refreshToken")
#Headers("Accept: application/json")
suspend fun refreshToken(#Body x: X): TokenResponseDto
}
I've a class where I use UserApi to do API call.
class TokenAuthenticator(
private val userApi: UserApi
) : Authenticator {
override fun authenticate(route: Route?, response: Response): Request? = synchronized(this) {
runBlocking { userApi.refreshToken() }
}
}
This far everything is fine, but now I want to Inject TokenAuthenticator class. If I remove constructor for testing purposes, I can see app running and everything is fine, but when I add userApi constructor variable - as I need it, I get and error.
I've NetworkModule that looks like this:
val networkModule = module {
single<UserApi> {
Retrofit.Builder()
.client(get(named("httpClient")))
.baseUrl(get<String>(named("...")))
.addConverterFactory(
...
)
.build()
.create(UserApi::class.java)
}
single(named("httpClient")) {
val tokenAuthenticator: TokenAuthenticator = get()
OkHttpClient.Builder()
.authenticator(tokenAuthenticator)
.connectTimeout(30, TimeUnit.SECONDS)
.readTimeout(30, TimeUnit.SECONDS)
.build()
}
single {
TokenAuthenticator(get())
}
}
Error:
at org.koin.core.instance.SingleInstanceFactory$get$1.invoke(SingleInstanceFactory.kt:53)
UPDATE: Someone advised to use a lambda function in TokenAuthenticator. I think this solution is more simplier.
class TokenAuthenticator(
private val userApi : () -> UserApi
) {
// ...
fun authenticate(...) {
userApi().refreshToken()
}
}
In this case you can define your koin definition like this.
single {
TokenAuthenticator {
get()
}
}
My answer was:
There may be better solutions but this is a rushed one. You may improve it.
Let's decouple TokenAuthenticator and UserApi. They will be connected later by a TokenRefresher.
interface TokenRefresher {
fun refreshToken()
}
class TokenAuthenticator(
private val tokenRefresher: TokenRefresher
) : Authenticator {
override fun authenticate(route: Route?, response: Response): Request? = synchronized(this) {
runBlocking { tokenRefresher.refreshToken() }
}
}
Add a token refresher into koin module.
val networkModule = module {
single<TokenRefresher> {
object : TokenRefresher {
// now use the userApi
override fun refreshToken() {
val userApi: UserApi = get()
userApi.refreshToken()
}
}
}
single<UserApi> {
Retrofit.Builder()
.client(get(named("httpClient")))
.baseUrl(get<String>(named("...")))
.addConverterFactory(
...
)
.build()
.create(UserApi::class.java)
}
single(named("httpClient")) {
val tokenAuthenticator: TokenAuthenticator = get()
OkHttpClient.Builder()
.authenticator(tokenAuthenticator)
.connectTimeout(30, TimeUnit.SECONDS)
.readTimeout(30, TimeUnit.SECONDS)
.build()
}
single {
TokenAuthenticator(get())
}
}
Hope it helps.
I am using a PreferenceScreen to set a auth key and a url which I want to use in my retrofit API service.
So to get the auth key I need to access SharedPreferences inside my API service. But to do so I need a context. How can I pass context to my retrofit instance?
Here is my API service:
private val sharedPreferences = PreferenceManager.getDefaultSharedPreferences(???)
private val BASE_URL = sharedPreferences.getString("api_url","")
private val TTN_KEY = sharedPreferences.getString("access_key","")
private val loggingInterceptor: HttpLoggingInterceptor =
HttpLoggingInterceptor().setLevel(HttpLoggingInterceptor.Level.BODY)
private val okHttpClient = OkHttpClient.Builder()
.addInterceptor { chain ->
val originalRequest = chain.request()
val newRequest = originalRequest.newBuilder()
.addHeader("Authorization", "key $TTN_KEY")
.build()
chain.proceed(newRequest)
}
.addInterceptor(loggingInterceptor)
.build()
private val retrofit = Retrofit.Builder()
.baseUrl(BASE_URL)
.addConverterFactory(MoshiConverterFactory.create())
.client(okHttpClient)
.build()
interface TTNApiService {
#GET("devices")
suspend fun getDevices(): List<String>
#GET("query/{device-id}")
suspend fun getDeviceValues(#Path("device-id") id: String): List<NetworkValue>
#GET("query")
suspend fun getValues(): List<NetworkValue>
}
// public object used to access the retrofit instance
object TTNApi {
val retrofitService: TTNApiService by lazy {
retrofit.create(TTNApiService::class.java)
}
}
A basic solution is to store application context in a class which extends Application class. Like this
class MyApp : Application() {
override fun onCreate() {
instance = this
super.onCreate()
}
companion object {
var instance: MyApp? = null
private set
val context: Context?
get() = instance
}
}
Then you can get the context in your file like this
private val sharedPreferences = PreferenceManager.getDefaultSharedPreferences(MyApp.context)
Also a preferred and proper way would be to use a dependency injection framework like Koin which is lightweight and easy to use
I think I've found a solution.
I don't know if its clean, but it works for now.
If you have any advice please let me know!
I refactored my interface like below
Changed my ViewModels to extend from AndroidViewModel instead of ViewModel so I can use the application context
every time I want to call the API I use TTNApiService.create(application).getDeviceValues(deviceId)
interface TTNApiService {
#GET("devices")
suspend fun getDevices(): List<String>
#GET("query/{device-id}")
suspend fun getDeviceValues(#Path("device-id") id: String): List<NetworkValue>
#GET("query")
suspend fun getValues(): List<NetworkValue>
companion object{
private lateinit var BASE_URL : String;
private lateinit var TTN_KEY : String;
fun create(context: Context): TTNApiService{
val sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context)
BASE_URL = sharedPreferences.getString("api_url","").toString()
TTN_KEY = sharedPreferences.getString("access_key","").toString()
val loggingInterceptor: HttpLoggingInterceptor =
HttpLoggingInterceptor().setLevel(HttpLoggingInterceptor.Level.BODY)
val okHttpClient = OkHttpClient.Builder()
.addInterceptor { chain ->
val originalRequest = chain.request()
val newRequest = originalRequest.newBuilder()
.addHeader("Authorization", "key $TTN_KEY")
.build()
chain.proceed(newRequest)
}
.addInterceptor(loggingInterceptor)
.build()
return Retrofit.Builder()
.baseUrl(BASE_URL)
.addConverterFactory(MoshiConverterFactory.create())
.client(okHttpClient)
.build()
.create(TTNApiService::class.java)
}
}
}
I am trying to have DI for creating a retrofit instance with KOIN
this is the module
val networkModule = module {
factory { provideRetrofit(get()) }
single { provideNetworkApi(get()) }
}
fun provideRetrofit(): Retrofit {
return Retrofit.Builder()
.baseUrl(NetworkConstant.BASE_URL)
.addConverterFactory(MoshiConverterFactory.create())
.client(OkHttpClient.Builder().build())
.build()
}
fun provideNetworkApi(retrofit: Retrofit): NetworkCall =
retrofit.create(NetworkCall::class.java)
In the application class
class BaseApp :Application() {
override fun onCreate() {
super.onCreate()
startKoin {
androidLogger()
androidContext(this#BaseApp)
modules(listOf(prefModule,networkModule))
}
}
}
The compiler is showing error on provideRetrofit(get()
Too many arguments for public fun provideRetrofit():
I was able to create DI for another class
val prefModule = module {
single { AppPreference(androidContext()) }
}
Can someone point me whats wrong here?
Was being caused because of get()
val networkModule = module {
factory { provideRetrofit() }
single { provideNetworkApi(get()) }
}
Would it be acceptable skip separation of Retrofit instance (what's benefit?) and simply combine it in the goal of creating your NetworkCall?
Here is an example of what I mean:
val appModule = module {
single {
val httpInterceptor = HttpLoggingInterceptor()
httpInterceptor.setLevel(HttpLoggingInterceptor.Level.BODY)
Retrofit.Builder()
.client(
OkHttpClient.Builder()
.addInterceptor(httpInterceptor).build()
)
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
.addConverterFactory(GsonConverterFactory.create())
.baseUrl("https://postman-echo.com/")
.build()
.create(PostmanEcho::class.java)
}
single {
PostmanEchoRepository(get(), get())
}
....
My PostmanEchoRepository is using equivalent of your NetworkCall as first param.
Details: https://github.com/oradkovsky/weird-data-input/blob/master/app/src/main/java/com/ror/weirddatainput/di/AppModule.kt
I am developing a news app and I want to add two modules in application class but I am getting the following exception.
java.lang.RuntimeException: Unable to create application yodgorbek.komilov.musobaqayangiliklari.di.application.SportNewsApplication: org.koin.core.error.DefinitionOverrideException: Already existing definition or try to override an existing one: [type:Single,primary_type:'yodgorbek.komilov.musobaqayangiliklari.internet.SportNewsInterface']
at android.app.ActivityThread.handleBindApplication(ActivityThread.java:5971)
at android.app.ActivityThread.access$1300(ActivityThread.java:206)
at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1700)
at android.os.Handler.dispatchMessage(Handler.java:106)
at android.os.Looper.loop(Looper.java:201)
at android.app.ActivityThread.main(ActivityThread.java:6820)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:547)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:922)
Caused by: org.koin.core.error.DefinitionOverrideException: Already existing definition or try to override an existing one: [type:Single,primary_type:'yodgorbek.komilov.musobaqayangiliklari.internet.SportNewsInterface']
at org.koin.core.registry.BeanRegistry.addDefinition(BeanRegistry.kt:144)
at org.koin.core.registry.BeanRegistry.saveDefinition(BeanRegistry.kt:101)
at org.koin.core.registry.BeanRegistry.saveDefinitions(BeanRegistry.kt:71)
at org.koin.core.registry.BeanRegistry.loadModules(BeanRegistry.kt:49)
at org.koin.core.KoinApplication.loadModulesAndScopes(KoinApplication.kt:66)
at org.koin.core.KoinApplication.modules(KoinApplication.kt:60)
at yodgorbek.komilov.musobaqayangiliklari.di.application.SportNewsApplication$onCreate$1.invoke(SportNewsApplication.kt:19)
at yodgorbek.komilov.musobaqayangiliklari.di.application.SportNewsApplication$onCreate$1.invoke(SportNewsApplication.kt:11)
at org.koin.core.context.GlobalContextKt.startKoin(GlobalContext.kt:72)
at yodgorbek.komilov.musobaqayangiliklari.di.application.SportNewsApplication.onCreate(SportNewsApplication.kt:16)
at android.app.Instrumentation.callApplicationOnCreate(Instrumentation.java:1155)
at android.app.ActivityThread.handleBindApplication(ActivityThread.java:5966)
... 8 more
below SportNewsApplication.kt class
class SportNewsApplication : Application() {
override fun onCreate() {
super.onCreate()
// Adding Koin modules to our application
startKoin {
// androidContext(this#SportNewsApplication)
modules(
listOf(appModules, bbcModules))
}
}
}
below appModules.kt
const val BASE_URL = "https://newsapi.org/"
val appModules = module {
// The Retrofit service using our custom HTTP client instance as a singleton
single {
createWebService<SportNewsInterface>(
okHttpClient = createHttpClient(),
factory = RxJava2CallAdapterFactory.create(),
baseUrl = BASE_URL
)
}
// Tells Koin how to create an instance of CatRepository
factory<NewsRepository> { (NewsRepositoryImpl(sportsNewsApi = get())) }
// Specific viewModel pattern to tell Koin how to build MainViewModel
viewModel { MainViewModel(newsRepository = get()) }
}
/* Returns a custom OkHttpClient instance with interceptor. Used for building Retrofit service */
fun createHttpClient(): OkHttpClient {
val client = OkHttpClient.Builder()
client.readTimeout(5 * 60, TimeUnit.SECONDS)
return client.addInterceptor {
val original = it.request()
val requestBuilder = original.newBuilder()
requestBuilder.header("Content-Type", "application/json")
val request = requestBuilder.method(original.method, original.body).build()
return#addInterceptor it.proceed(request)
}.build()
}
/* function to build our Retrofit service */
inline fun <reified T> createWebService(
okHttpClient: OkHttpClient,
factory: CallAdapter.Factory, baseUrl: String
): T {
val retrofit = Retrofit.Builder()
.baseUrl(baseUrl)
.addConverterFactory(GsonConverterFactory.create(GsonBuilder().setLenient().create()))
.addCallAdapterFactory(CoroutineCallAdapterFactory())
.addCallAdapterFactory(factory)
.client(okHttpClient)
.build()
return retrofit.create(T::class.java)
}
below bbcModules.kt
const val base_url = "https://newsapi.org/"
val bbcModules = module {
// The Retrofit service using our custom HTTP client instance as a singleton
single {
createBBCWebService<SportNewsInterface>(
okHttpClient = createBBCHttpClient(),
factory = RxJava2CallAdapterFactory.create(),
baseUrl = base_url
)
}
// Tells Koin how to create an instance of CatRepository
factory<BBCRepository> { (BBCRepositoryImpl(bbcsportNewsApi = get())) }
// Specific viewModel pattern to tell Koin how to build MainViewModel
viewModel { BBCSportViewModel(bbcRepository = get()) }
}
/* Returns a custom OkHttpClient instance with interceptor. Used for building Retrofit service */
fun createBBCHttpClient(): OkHttpClient {
val client = OkHttpClient.Builder()
client.readTimeout(5 * 60, TimeUnit.SECONDS)
return client.addInterceptor {
val original = it.request()
val requestBuilder = original.newBuilder()
requestBuilder.header("Content-Type", "application/json")
val request = requestBuilder.method(original.method, original.body).build()
return#addInterceptor it.proceed(request)
}.build()
}
/* function to build our Retrofit service */
inline fun <reified T> createBBCWebService(
okHttpClient: OkHttpClient,
factory: CallAdapter.Factory, baseUrl: String
): T {
val retrofit = Retrofit.Builder()
.baseUrl(baseUrl)
.addConverterFactory(GsonConverterFactory.create(GsonBuilder().setLenient().create()))
.addCallAdapterFactory(CoroutineCallAdapterFactory())
.addCallAdapterFactory(factory)
.client(okHttpClient)
.build()
return retrofit.create(T::class.java)
}
what I have tried
1.clean rebuild and invalidate cache restart and other StackOverflow answers
it did not solve my problem.
I have followed following link https://github.com/InsertKoinIO/koin/issues/420 as well
I want to know what I have to do in order to solve the exception.
It looks like you try to create two instances of OkHttpClient in separate modules. You can use override parametr for a module for override one instance by other
(module(override = true))
but in this case it is incorrect. You must have tow different instances OkHttpClient. For this, you can use named instance
single<OkHttpClient>(named("WebService")) {
createWebService<SportNewsInterface>(
okHttpClient = createHttpClient(),
factory = RxJava2CallAdapterFactory.create(),
baseUrl = BASE_URL
)
}
and
single<OkHttpClient>(named("BBCWebService")) {
createBBCWebService<SportNewsInterface>(
okHttpClient = createBBCHttpClient(),
factory = RxJava2CallAdapterFactory.create(),
baseUrl = base_url
)
}
If need inject specific client need to use
SomeClassNeedDependency(get(named("WebService")))
More information
Try to use named. Here is a doc
below appModules.kt
single(named("appModules")) {
createBBCWebService<SportNewsInterface>(
okHttpClient = createBBCHttpClient(),
factory = RxJava2CallAdapterFactory.create(),
baseUrl = base_url
)
}
factory<NewsRepository> { (NewsRepositoryImpl(sportsNewsApi = get(named("appModules")))) }
below bbcModules.kt
single(named("bbcModules")) {
createBBCWebService<SportNewsInterface>(
okHttpClient = createBBCHttpClient(),
factory = RxJava2CallAdapterFactory.create(),
baseUrl = base_url
)
}
factory<BBCRepository> { (BBCRepositoryImpl(bbcsportNewsApi = get(named("bbcModules")))) }
I am facing with SharedPreferences problem. I would like to know how I can call SharedPreferences inside Retrofit. I mean, I have this following file :
#Module
class NetworkModule {
#Provides
internal fun provideGson(): Gson {
return GsonBuilder().create()
}
#Provides
internal fun provideOkHttpClient(): OkHttpClient {
return OkHttpClient.Builder().addInterceptor { chain ->
val original = chain.request()
val requestBuilder = original.newBuilder().addHeader("Accept", "application/json")
val request = requestBuilder.method(original.method(), original.body()).build()
chain.proceed(request)
}.build()
}
#Provides
internal fun provideRetrofit(gson: Gson, okHttpClient: OkHttpClient): Retrofit {
return Retrofit.Builder()
.addConverterFactory(GsonConverterFactory.create(gson))
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
.baseUrl(BASE_URL)
.client(okHttpClient)
.build()
}
}
And in my Presenter, I have this following code:
override fun getSavedToken() {
mToken = mSharedPreferences.getString(TOKEN, TOKEN_UNAVAILABLE)
}
...
inner class GetAccessTokenSubscriber : ResourceObserver<AccessTokenBean>() {
override fun onNext(#NonNull accessToken: AccessTokenBean) {
mSharedPreferences.edit().putString(TOKEN, accessToken.token).apply()
getInformation()
}
override fun onError(#NonNull e: Throwable) {
mView?.displayError()
}
override fun onComplete() {
// Nothing to do
}
}
Currently, to set the token I put the Bearer $token in my repository / service
// Repository
val newToken = "Bearer $token"
return mService.getInfos(newToken)
// Service
fun getInfos(#Header("Authorization") token: String
I would like to know how I can put the Bearer + token inside my NetworkModule file?
Thank you for your time.
If you want to place the value on the interceptor, just call your SharedPreferences instance on the interceptor provider:
#Provides
internal fun provideOkHttpClient(sharedPrefs: SharedPrefs): OkHttpClient {
return OkHttpClient.Builder().addInterceptor { chain ->
val original = chain.request()
val requestBuilder = original.newBuilder().addHeader("Accept", "application/json")
val request = requestBuilder.method(original.method(), original.body()).build()
chain.proceed(request)
}.build()
}
Now dagger will look for that, but it won't find it, giving you an error. In that case, if you network module is also a singleton too just add a includes = [PreferencesModule::class], if not, you may need to set the current component dependent on the Singleton where you preferences module is located.