How to add correctly two modules in koin? - android

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")))) }

Related

OkHttp client generates diffrent URL for retrofit

I am developing a simple app to fetch now playing movie details from https://www.themoviedb.org/ API.
This is the URL to which I need to perform the API call.
https://api.themoviedb.org/3/movie/now_playing?api_key=<<api_key>>
I am using retrofit to make the API call like this.
#GET("/movie/now_playing")
fun getNowPlayingMovies(): Single<List<MovieData>>
and I am using Base Url and API key as variables in the client class.
private val BASE_URL = "https://api.themoviedb.org/3/"
private val API_KEY = "xxxxxxxxxxxxxxxxxxxxxxxxxxx"
Since the best way to add the api key as a query parameter to the URL is by using a request Interceptor , I created an intercepted to intercept the request and add the api key as a query parameter.
private fun getInterceptor() : Interceptor{
if (requestInterceptor == null){
requestInterceptor = Interceptor{
val url = it.request()
.url
.newBuilder()
.addQueryParameter("api_key" , API_KEY)
.build()
val request = it.request()
.newBuilder()
.url(url)
.build()
return#Interceptor it.proceed(request)
}
}
return requestInterceptor
}
Then added this interceptor along with logging interceptor to the OkHttp Client.
private fun getOkHttpClient() : OkHttpClient{
var httLog : HttpLoggingInterceptor = HttpLoggingInterceptor()
httLog.setLevel(HttpLoggingInterceptor.Level.BODY)
val okHttpClient = OkHttpClient.Builder()
.addInterceptor(getInterceptor()).addInterceptor(httLog)
.connectTimeout(60 , TimeUnit.SECONDS)
.build()
return okHttpClient
}
And then build the retrofit client. In which I add the base URL.
var retrofit : Retrofit = Retrofit.Builder()
.baseUrl(BASE_URL)
.client(getOkHttpClient())
.addConverterFactory(getGsonConverterFactory())
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
.build()
The problem is ,
the entire URL made using the BASE_URL and the api as a Query Paramter , Should be like this
https://api.themoviedb.org/3/movie/now_playing?api_key=**********
But in the logging interceptor I am getting this as the url
https://api.themoviedb.org/movie/now_playing?api_key=**********
which has a different BASE_URL than the one I provided in retrofit. It is giving me a 404 error.
I cannot seem to figure out why this is causing.
I logged on different places and I believe the request Interceptor is intercepting a different URL than the BASE_URL.
This is my entire codebase, it would be very helpful if there are any other improvements in the code , I am new to android development and Kotlin. Thank You.
interface MoviesApiServiceRx {
#GET("/movie/now_playing")
fun getNowPlayingMovies(): Single<List<MovieData>>
}
class MoviesRetrofitClient() {
private val BASE_URL = "https://api.themoviedb.org/3/"
private val API_KEY = "9a976526fce8c29aaa35eb4a1e654d3c"
private var moviesApiServiceRx : MoviesApiServiceRx
private var gsonConverterFactory : GsonConverterFactory
private var requestInterceptor : Interceptor
init {
moviesApiServiceRx = getMoviesApiServiceRx()
gsonConverterFactory = getGsonConverterFactory()
requestInterceptor = getInterceptor()
}
private fun getInterceptor() : Interceptor{
if (requestInterceptor == null){
requestInterceptor = Interceptor{
val url = it.request()
.url
.newBuilder()
.addQueryParameter("api_key" , API_KEY)
.build()
val request = it.request()
.newBuilder()
.url(url)
.build()
return#Interceptor it.proceed(request)
}
}
return requestInterceptor
}
private fun getGsonConverterFactory() : GsonConverterFactory{
if (gsonConverterFactory == null){
gsonConverterFactory = GsonConverterFactory.create();
}
return gsonConverterFactory
}
private fun getOkHttpClient() : OkHttpClient{
var httLog : HttpLoggingInterceptor = HttpLoggingInterceptor()
httLog.setLevel(HttpLoggingInterceptor.Level.BODY)
val okHttpClient = OkHttpClient.Builder()
.addInterceptor(getInterceptor()).addInterceptor(httLog)
.connectTimeout(60 , TimeUnit.SECONDS)
.build()
return okHttpClient
}
private fun getMoviesApiServiceRx() : MoviesApiServiceRx{
if (moviesApiServiceRx == null){
var retrofit : Retrofit = Retrofit.Builder()
.baseUrl(BASE_URL)
.client(getOkHttpClient())
.addConverterFactory(getGsonConverterFactory())
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
.build()
moviesApiServiceRx = retrofit.create(MoviesApiServiceRx::class.java)
}
return moviesApiServiceRx
}
fun getNowPlayingMovies(): Single<List<MovieData>> {
return getMoviesApiServiceRx().
getNowPlayingMovies()
}
}
Change
#GET("/movie/now_playing")
to
#GET("movie/now_playing")
The / at the beginning refers to the root so it will replace whatever there is after the service name.
two options
interface MoviesApiServiceRx {
#GET("/3/movie/now_playing")
fun getNowPlayingMovies(): Single<List<MovieData>>
}
private val BASE_URL = "https://api.themoviedb.org/"
or
interface MoviesApiServiceRx {
#GET("movie/now_playing")
fun getNowPlayingMovies(): Single<List<MovieData>>
}
private val BASE_URL = "https://api.themoviedb.org/3/"

Android make POST request with retrofit

I'm trying to make my first POST request to make the user login using retrofit library, but it's not working and i don't understand why. If i make a GET request it works, but with POST something gone wrong and i don't understand why. My API run on localhost webserver
My code of the LoginService:
private const val BASE_URL = "http://localhost:10000/api/"
/**
* Build the Moshi object that Retrofit will be using, making sure to add the Kotlin adapter for
* full Kotlin compatibility.
*/
private val moshi = Moshi.Builder()
.add(KotlinJsonAdapterFactory())
.build()
/**
* Use the Retrofit builder to build a retrofit object using a Moshi converter with our Moshi
* object.
*/
private val retrofit = Retrofit.Builder()
.addConverterFactory(MoshiConverterFactory.create(moshi))
.baseUrl(BASE_URL)
.build()
interface LoginApiService {
#Headers("Content-Type: application/json")
#POST("login")
suspend fun makeLogin(#Body usr: User): LoginResponse
}
/**
* A public Api object that exposes the lazy-initialized Retrofit service
*/
object LoginApi {
val retrofitService : LoginApiService by lazy { retrofit.create(LoginApiService::class.java) }
}
code of the LoginResponse class
data class LoginResponse(
val token: String,
val expiration: Date,
val role: Int)
code of the User class:
data class User(
val mail: String,
val pw: String
) : Parcelable
Code of the ViewModel that make the request:
private fun makeLogin(email: String, password: String) {
viewModelScope.launch {
try {
val usr = User(email, password)
val rsp = LoginApi.retrofitService.makeLogin(usr)
_isLogged.value = true
} catch (ex: Exception) {
_status.value = LoginStatus.ERROR
}
}
}
Can someone help me to solve this please? it seems that the request it's not sended.
my retrofit call generate this error in logcat in the try-catch block
java.lang.IllegalArgumentException: Unable to create converter for class com.example.ticketapp.network.LoginResponse
for method LoginApiService.makeLogin
Default Retrofit's timeout is 10sec. You can fix it like this:
val client = OkHttpClient.Builder()
.connectTimeout(30, TimeUnit.SECONDS)
.readTimeout(30, TimeUnit.SECONDS)
.writeTimeout(30, TimeUnit.SECONDS)
.build()
val retrofit = Retrofit.Builder()
...
.client(client)
.build()
Here I set it to 30secs, but you can use any number and TimeUnit you want.
UPD:
You can store Retorfit builder in a separate file like this:
interface WebService {
companion object {
fun <T> build(clazz: Class<T>): T {
val client = OkHttpClient.Builder()
...
.build()
val retrofit = Retrofit.Builder()
...
.build()
return retrofit.create(clazz)
}
}
}
Then you can have multiple ApiService interfaces. And use them like this:
val myApiService = WebService.build(MyApiServiceInterface::class.java)
myApiService.myRequestFunction()
Try to add
android:usesCleartextTraffic="true"
Into your application tag in manifest

Proper way to use SharedPreferences with retrofit singleton

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)
}
}
}

SharedPreferences instance inside NetworkModule

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.

Error while resolving instance for class Koin

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!!!

Categories

Resources