Here is my Retrofit Interface and creation code:
interface SSApi {
companion object {
private fun create(): SSApi {
val httpClient = OkHttpClient().newBuilder()
val networkInterceptor = Interceptor { chain ->
val request = chain.request()?.newBuilder()?.addHeader("api-key", SSConstants.API_KEY)?.build()
chain.proceed(request!!)
}
val loggingInterceptor = HttpLoggingInterceptor()
loggingInterceptor.level = HttpLoggingInterceptor.Level.BODY
httpClient.addNetworkInterceptor(networkInterceptor).addInterceptor(loggingInterceptor)
val retrofit = Retrofit.Builder()
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
.addConverterFactory(GsonConverterFactory.create())
.baseUrl(SSConstants.BASE_URL)
.client(httpClient.build())
.build()
return retrofit.create(SSApi::class.java)
}
val api by lazy {
SSApi.create()
}
var disposable: Disposable? = null
}
#GET
fun getWeatherInfo(#Url url: String): Observable<OpenWeatherMapInfo>
}
And here is how I use the disposable:
private fun getWeather() {
disposable = api
.getWeatherInfo(SSConstants.OPEN_WEATHER_MAP_API_ENDPOINT)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(
{ results -> Log.i("Dale", results.toString())},
{ error -> Log.i("Dale", error.message)}
)
}
When I execute the request, I can see that it my OPEN_WEATHER_MAP_API_ENDPOINT still appends to my baseUrl.
Here is my Constants class for reference:
object SSConstants {
const val OPEN_WEATHER_MAP_API_ENDPOINT = "api.openweathermap.org/data/2.5/weather?q=Catbalogan,PH&units=metric"
const val BASE_URL = "https://api.xxx.xxx/"
}
Your issue is that you didn't provide the full URL in the dynamic call, and that's why Retrofit is trying to make the call relative to the base url still. Just add https:// to the dynamic URL:
const val OPEN_WEATHER_MAP_API_ENDPOINT = "https://api.openweathermap.org/data/2.5/weather?q=Catbalogan,PH&units=metric"
Here is an article discussing how the dynamic URL is resolved in different scenarios, for further reference.
Related
I have ServiceBuilder object for init retrofit instance
object ServiceBuilder {
//private var url: String? = null
var url = "http://no-google.com" // The default link
fun loadUrl(url: String): ServiceBuilder{
this.url = url
return this
}
private var logger = HttpLoggingInterceptor().setLevel(HttpLoggingInterceptor.Level.BODY)
val headerInterceptor = object: Interceptor {
override fun intercept(chain: Interceptor.Chain): Response {
var request = chain.request()
request = request.newBuilder()
.addHeader("x-device-type", Build.DEVICE)
.addHeader("Accept-Language", Locale.getDefault().language)
.build()
val response = chain.proceed(request)
return response
}
}
// Create OkHttp Client
private val okHttp = OkHttpClient.Builder()
.callTimeout(5, TimeUnit.SECONDS)
.addInterceptor(headerInterceptor)
.addInterceptor(logger)
// Create Retrofit Builder
private val builder = Retrofit.Builder()
.baseUrl(url)
.addConverterFactory(GsonConverterFactory.create())
.client(okHttp.build())
// Create Retrofit Instance
private val retrofit = builder.build()
fun <T> buildService(serviceType: Class<T>): T {
return retrofit.create(serviceType)
}
}
getUrlFromServer() method inside MainActivity
private fun getUrlFromServer(str: String){
val destinationService = ServiceBuilder
.loadUrl("http://google.com") // <-- This call can not reply url into ServiceBuilder object
.buildService(DestinationService::class.java)
val requestCall = destinationService.getList()
requestCall.enqueue(object: Callback<List<Destination>> {
override fun onResponse(
call: Call<List<Destination>>,
response: Response<List<Destination>>
) {
if (response.isSuccessful){
val destinationList = response.body()
//Toast.makeText(this, destinationList.toString(), Toast.LENGTH_LONG)
}
}
override fun onFailure(call: Call<List<Destination>>, t: Throwable) {
TODO("Not yet implemented")
}
})
}
I don't understand why the loadUrl() function which is inside ServiceBuilder can not load url. I need to send url from MainActivity to ServiceBuilder object.
Please tell me how I should decide this issue in good style
Because create retrofit instance, take happen before ServiceBuilder loadUrl function.
Actually retrofit instance, always is created with "http://no-google.com" url!!
fun <T> buildService(serviceType: Class<T>): T {
// Create Retrofit Builder
private val builder = Retrofit.Builder()
.baseUrl(url)
.addConverterFactory(GsonConverterFactory.create())
.client(okHttp.build())
// Create Retrofit Instance
private val retrofit = builder.build()
return retrofit.create(serviceType)
}
I am developing new android app where I want to add baseurl and api_key in koin module retrofit but I am confused
Ending Point: https://api-aws-eu-qa-1.auto1-test.com/v1/car-types/manufacturer?wa_key=coding-puzzle-client-449cc9d&page=0&pageSize=15
API Key: coding-puzzle-client-449cc9d
Base Url: https://api-aws-eu-qa-1.auto1-test.com/
I want to add base_url and api_key correctly in my modules.kt file correctly so that later I can fetch data correctly from server if you check my modules.kt first I am calling api_key then base url. and setup my interface where I am call get method in interface following way
interface ApiInterface {
#GET("v1/car-types/manufacturer?")
suspend fun getCarResponse(): Call<CarManufactureResponse>
}
below CarManifacturerResponse
data class CarManufactureResponse(
#SerializedName("page")
val page: Int,
#SerializedName("pageSize")
val pageSize: Int,
#SerializedName("totalPageCount")
val totalPageCount: Int,
#SerializedName("mkda")
val mkda: ManufacturerId
)
below ManifacturerId
data class ManufacturerId(
#SerializedName("020")
val x020: String,
#SerializedName("040")
val x040: String,
#SerializedName("042")
val x042: String,
#SerializedName("043")
val x043: String,
#SerializedName("057")
val x057: String,
#SerializedName("060")
val x060: String,
#SerializedName("095")
val x095: String,
#SerializedName("107")
val x107: String,
#SerializedName("125")
val x125: String,
#SerializedName("130")
val x130: String,
#SerializedName("141")
val x141: String,
#SerializedName("145")
val x145: String,
#SerializedName("150")
val x150: String,
#SerializedName("157")
val x157: String,
#SerializedName("160")
val x160: String
)
below my Modules.kt koin setup module where I want to pass baseurl and api_key
val viewModels = module {
//viewModel { CarViewModel(get()) }
}
val apiModule = module {
single {
val tokenInterceptor = Interceptor { chain ->
val request =
chain
.request()
.newBuilder()
.addHeader(
"API_KEY",Constants.API_KEY
)
.build()
chain.proceed(request)
}
val logInterceptor: HttpLoggingInterceptor = HttpLoggingInterceptor().apply {
this.level = HttpLoggingInterceptor.Level.BODY
}
val okHttpClient =
OkHttpClient.Builder()
.addInterceptor(tokenInterceptor)
.addInterceptor(logInterceptor)
.build()
val retrofit =
Retrofit.Builder()
.client(okHttpClient)
.baseUrl(Constants.BASE_URL)
.addConverterFactory(GsonConverterFactory.create())
.addCallAdapterFactory(CoroutineCallAdapterFactory())
.build()
retrofit.create(ApiInterface::class.java)
}
}
below Constants.kt
object Constants {
const val API_KEY = "wa_key=coding-puzzle-client-449cc9d"
const val BASE_URL = "https://api-aws-eu-qa-1.auto1-test.com/"
}
how can I pass correctly baseurl and apiKey in koin module so that I can implement retrofit logic correctly
Use the code below to get your desired url:
#GET("v1/car-types/manufacturer?{apiKey}")
suspend fun getCarResponse( #Path("apiKey") type: String,#Query("page") page:String,#Query("pageSize") pageSize:String): Call<CarManufactureResponse>
I'm building an app for a company using MVVM & clean architecture so I've created 3 modules, the app module (presentation layer), the data module (data layer) & the domain module (domain/interactors layer). Now, in my data module, I'm using Retrofit and Gson to automatically convert the JSON I'm receiving from a login POST request to my kotlin data class named NetUserSession that you see below. The problem I'm having is that the logging interceptor prints the response with the data in it normally but the response.body() returns an empty NetUserSession object with null values which makes me think that the automatic conversion isn't happening for some reason. Can somebody please tell me what I'm doing wrong here?
KoinModules:
val domainModule = module {
single<LoginRepository> {LoginRepositoryImpl(get())}
single { LoginUseCase(get()) }
}
val presentationModule = module {
viewModel { LoginViewModel(get(),get()) }
}
val dataModule = module {
single { ApiServiceImpl().getApiService() }
single { LoginRepositoryImpl(get()) }
}
}
Api interface & retrofit:
interface ApiService {
#POST("Login")
fun getLoginResult(#Body netUser: NetUser) : Call<NetUserSession>
#GET("Books")
fun getBooks(#Header("Authorization") token:String) : Call<List<NetBook>>
}
class ApiServiceImpl {
fun getApiService(): ApiService {
val logging = HttpLoggingInterceptor()
logging.setLevel(HttpLoggingInterceptor.Level.BODY)
//TODO:SP Remove the interceptor code when done debugging
val client: OkHttpClient = OkHttpClient.Builder()
.addInterceptor(logging)
.build()
val retrofit = Retrofit.Builder().baseUrl(baseUrl)
.addConverterFactory(GsonConverterFactory.create())
.client(client)
.build()
// tell retrofit to implement the interface of our api
return retrofit.create(ApiService::class.java)
}
}
NetUserSession:
data class NetUserSession(
#SerializedName("expires_in")
val expires_in: Int,
#SerializedName("token_type")
val token_type: String,
#SerializedName("refresh_token")
val refresh_token: String,
#SerializedName("access_token")
val access_token: String
) {
fun toUserSession(): UserSession = UserSession(
expiresIn = expires_in,
tokenType = token_type,
refreshToken = refresh_token,
accessToken = access_token
)
}
UserSession in domain:
data class UserSession(
val expiresIn:Int,
val tokenType:String,
val refreshToken:String,
val accessToken:String
)
LoginRepositoryImpl where the error occurs:
class LoginRepositoryImpl(private val apiService: ApiService) : LoginRepository {
override suspend fun login(username:String,password:String): UserSession? = withContext(Dispatchers.IO){
val response = apiService.getLoginResult(NetUser(username,password)).awaitResponse()
println("THE RESPONSE WAS : ${response.body()}")
return#withContext if(response.isSuccessful) response.body()?.toUserSession() else null
}
}
LoggingInterceptor result after the 200-OK:
{"expires_in":3600,"token_type":"Bearer","refresh_token":"T1amGR21.IdKM.5ecbf91162691e15913582bf2662e0","access_token":"T1amGT21.Idup.298885bf38e99053dca3434eb59c6aa"}
Response.body() print result:
THE RESPONSE WAS : NetUserSession(expires_in=0, token_type=null, refresh_token=null, access_token=null)
Any ideas what I'm failing to see here?
After busting my head for hours, the solution was to simply change the model class's members from val to var like so :
data class NetUserSession(
#SerializedName("expires_in")
var expires_in: Int = 0,
#SerializedName("token_type")
var token_type: String? = null,
#SerializedName("refresh_token")
var refresh_token: String? = null,
#SerializedName("access_token")
var access_token: String? = null
) {
fun toUserSession(): UserSession = UserSession(
expiresIn = expires_in,
tokenType = token_type!!,
refreshToken = refresh_token!!,
accessToken = access_token!!
)
}
my application crashes when I have no internet connection : I am looking for a method that handles any exception form the retrofit instance like server is not found exception Timeout No internet connection
RequestRepository : my repository which contain all my functions
class RequestRepository {
/** suspend function to get the result of token request*/
suspend fun getToken(userLoginModel: UserLoginModel): Response<TokenResponse> {
return ApiService.APILogin.getToken(userLoginModel)
}
ApiService : contain my Retofit instance
object ApiService {
private var token: String = ""
fun setToken(tk: String) {
token = tk
}
private val okHttpClient = OkHttpClient.Builder().connectTimeout(20, TimeUnit.SECONDS)
.readTimeout(20, TimeUnit.SECONDS).addInterceptor { chain ->
val chainRequest = chain.request()
val requestBuilder = chainRequest.newBuilder()
.addHeader("authorization", "Token $token")
.method(chainRequest.method, chainRequest.body)
val request = requestBuilder.build()
chain.proceed(request)
}.build()
var gson = GsonBuilder()
.setLenient()
.create()
private val retrofit by lazy {
Retrofit.Builder()
.baseUrl(LOGIN_URL)
.addConverterFactory(GsonConverterFactory.create(gson))
.client(okHttpClient)
.build()
}
val API: WebServicesApi by lazy {
retrofit.create(WebServicesApi::class.java)
}
WebServicesApi : my interface which contain my requests
interface WebServicesApi {
/** get the token from the API*/
#POST("user/login/")
suspend fun getToken(#Body userLoginModel: UserLoginModel): Response<TokenResponse>
}
LoginViewModel : my viewModel class
class LoginViewModel(private val repository: RequestRepository) : ViewModel() {
var tokenResponse: MutableLiveData<Response<TokenResponse>> = MutableLiveData()
/** using coroutine in getToken function to get the token */
fun getToken(userLoginModel: UserLoginModel) {
viewModelScope.launch(Dispatchers.IO) {
val tResponse = repository.getToken(userLoginModel)
tokenResponse.postValue(tResponse)
Log.d(TAG, "getToken: ${userLoginModel.password}")
}
}
}
You can add a Interceptor for handle error like this:
class GlobalErrorInterceptor : Interceptor {
override fun intercept(chain: Interceptor.Chain): Response {
val request = chain.request()
try {
val response = chain.proceed(request)
if (!response.isSuccessful) {
val statusCode = response.code
when (statusCode) {
//Your handle status code in here
}
}
return response
} catch (ex: IOException) {
// You can replace my code with your exception handler code
return Response.Builder().request(chain.request()).protocol(Protocol.HTTP_1_1)
.message("Can't connect!").code(500).body(
ResponseBody.create(
"application/json; charset=utf-8".toMediaTypeOrNull(),
""
)
).build()
}
}
}
And you must add this class to OkHttpBuider:
val httpBuilder = OkHttpClient.Builder()
......
httpBuilder.addInterceptor(GlobalErrorInterceptor())
I'm trying to get user name from gitHub Api but retrofit response return always null. When I'm trying to show user name in toast I see : null. I tried change retrofit path but it didn't work. Everything looks fine but I don't know why I get this error all the time.
interface GitHubApi{
#GET("/users/{user}")
fun getUser(#Path("user") user: String): Call<User>
companion object Factory {
fun getClient(): GitHubApi {
val url = "https://api.github.com/"
val interceptor = HttpLoggingInterceptor()
.apply { level = HttpLoggingInterceptor.Level.BODY }
val okHttpClient = OkHttpClient.Builder()
.addInterceptor(interceptor)
.build()
val retrofit = Retrofit.Builder()
.baseUrl(url)
.addConverterFactory(GsonConverterFactory.create())
.client(okHttpClient)
.build()
return retrofit.create(GitHubApi::class.java)
}
}}
user model:
data class User(val userName: String)
MainActivity:
private fun createApiService() : GitHubApi{
val url = "https://api.github.com/"
val interceptor = HttpLoggingInterceptor()
.apply { level = HttpLoggingInterceptor.Level.BODY }
val okHttpClient = OkHttpClient.Builder()
.addInterceptor(interceptor)
.build()
val retrofit = Retrofit.Builder()
.baseUrl(url)
.addConverterFactory(GsonConverterFactory.create())
.client(okHttpClient)
.build()
return retrofit.create(GitHubApi::class.java)
}
private fun loadData() {
val api = GitHubApi.getClient()
api.getUser("fabpot").enqueue(object : Callback<User> {
override fun onFailure(call: Call<User>, t: Throwable) {
t.printStackTrace()
}
override fun onResponse(
call: Call<User>,
response: Response<User>
) {
if (!response.isSuccessful) {
runOnUiThread { showErrorMessage(response.code().toString()) }
}
response.body()?.let { showErrorMessage(it.repoName) }
}
})
}
There is no key userName exist in github api.
try editing your data class this way.
data class User(val name: String)
Try removing the beginning slash "/" in your #GET method.