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)
}
Related
I'm trying to learn coroutines with retrofit and hilt.
There is simple api github https://api.github.com/users/JakeWharton/repos
But my code give in log:
D/OkHttp: --> GET https://api.github.com/users/JakeWharton/repos
D/OkHttp: --> END GET
without any reponse, despite the fact that using postman I get list with repos.
In my function loadData() debugger stop on the 1st lane, it doesn't come to println, something is wrong but don't know what.
my codes:
#Provides
fun provideGitHubService(retrofit: Retrofit): GitHubService{
return retrofit.create(GitHubService::class.java)
}
#Provides
fun provideOkHttpClient(): OkHttpClient {
val loggingInterceptor = HttpLoggingInterceptor()
loggingInterceptor.level = HttpLoggingInterceptor.Level.BODY
return OkHttpClient
.Builder()
.addInterceptor(loggingInterceptor)
.build()
}
#Provides
fun provideRetrofit(okHttpClient: OkHttpClient): Retrofit {
return Retrofit.Builder()
.baseUrl("https://github.com") // don't know how to remove it but it will be override anyway
.addConverterFactory(GsonConverterFactory.create())
.client(okHttpClient)
.build()
}
private fun getRepos() {
viewModelScope.launch {
loadData()
}
}
private suspend fun loadData() {
val response = service.getRepos()
println(response). //debugger doesn't come here
}
interface GitHubService {
#GET("https://api.github.com/users/JakeWharton/repos")
suspend fun getRepos() : Call<List<User>>
}
data class User(
#SerializedName("name") val name: String
)
You don't need Call when using suspend. Please update the getRepos to:
// annotations omitted
suspend fun getRepos() : List<User>
I think you did something wrong in the instance of retrofit. try this.
class User {
#Expose
#SerializedName("name")
var name: String? = null
}
interface GitHubService {
#GET("users/JakeWharton/repos")
suspend fun getRepos(): Call<List<User>>
}
fun provideGitHubService(retrofit: Retrofit): GitHubService{
return retrofit.create(GitHubService::class.java)
}
class Example {
private val TAG = "Example"
/* OKHTTP CLIENT */
private fun provideOkHttpClient(): OkHttpClient {
val loggingInterceptor = HttpLoggingInterceptor()
loggingInterceptor.level = HttpLoggingInterceptor.Level.BODY
return OkHttpClient
.Builder()
.addInterceptor(loggingInterceptor)
.build()
}
/* RETROFIT INSTANCE */
private fun provideRetrofit(): Retrofit {
return Retrofit.Builder()
.baseUrl("https://api.github.com/") // don't know how to remove it but it will be override anyway
.addConverterFactory(GsonConverterFactory.create())
.client(provideOkHttpClient())
.build()
}
/* LOADING DATA */
suspend fun loadData() {
val apiInterface = provideGitHubService(provideRetrofit())
val call: Call<List<User>> = apiInterface.getRepos()
call.enqueue( object : Callback<List<User>> {
override fun onResponse(call: Call<List<User>>, response: Response<List<User>>) {
for (users in response.body()!!){
Log.e(TAG, "NAME: ${users.name}")
}
}
override fun onFailure(call: Call<List<User>>, t: Throwable) {
Log.e(TAG, "onFailure: ${t.message}")
}
})
}
}
I want to pass = sign in request params as string. It coverts into %3D upon calling api.
You need to use custom interceptor as below:
class MyInterceptor: Interceptor {
override fun intercept (chain: Interceptor.Chain): Response {
val request = chain.request()
val unEncodedStringUrl = request.url.toString().replace("%3D", "=")
var newRequest = request.newBuilder().url(unEcodedStringUrl).build()
return chain.proceed(newRequest)
}
}
And then use it for OkHttpClient of the retrofit:
fun getOkHttpClient(
myInterceptor: MyInterceptor,
loggingInterceptor: HttpLoggingInterceptor
): OkHttpClient {
return OkHttpClient().newBuilder().addInterceptor(myInterceptor).also {
if (BuildConfig.DEBUG) {
it.addInterceptor(loggingInterceptor)
}
}.build()
}
Finally, use this OkHttpClient for retrofit as below:
fun getRetrofit(moshi: Moshi): Retrofit {
return Retrofit.Builder()
.baseUrl(StaticConstants.baseUrl)
.client(getOkHttpClient())
.addConverterFactory(
MoshiConverterFactory.create(moshi)
)
.build()
}
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.
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.