When I was having an Api Key then I used the following below code to extract the Json data from it.
Now I want to fetch Json data from https://api.coingecko.com/api/v3/exchanges and I don't have any Api Key or query to pass.How can I do it using RetroFit?
import retrofit2.Call
import retrofit2.Retrofit
import retrofit2.http.GET
import retrofit2.create
import retrofit2.converter.gson.GsonConverterFactory
import retrofit2.http.Query
const val BASE_URL = "https://newsapi.org/"
const val API_KEY = "5f60ae62gcbc4bdaa0d15164d7f1275b"
interface NewsInterface {
#GET("v2/top-headlines?apiKey=$API_KEY")
fun getHeadLines(#Query("country")country:String): Call<News>
}
object NewsService {
val newsInstance :NewsInterface
init {
val retrofit: Retrofit= Retrofit.Builder()
.baseUrl(BASE_URL).addConverterFactory(GsonConverterFactory.create()).build()
newsInstance = retrofit.create(NewsInterface::class.java)
}
}
You API fun should be as,
#GET("api/v3/exchanges")
fun getExchanges(): Call<Response>
Related
learning about retrofit but couldn't write the tests for it. I came from jest background and struggling to test two things:
that the call was making to a specific end point and its status.
import okhttp3.ResponseBody
import retrofit2.Response
import retrofit2.Retrofit
import retrofit2.converter.gson.GsonConverterFactory
import retrofit2.http.GET
import retrofit2.http.Query
interface WeatherAPI {
#GET("current.json")
suspend fun getCurrentWeatherData(#Query("key") apiKey: String, #Query("q") cityName: String, #Query("qui") quiValue: String): Response<ResponseBody>
companion object {
private const val BASE_URL = "http://api.weatherapi.com/v1/"
val instance: WeatherAPI by lazy {
Retrofit.Builder()
.baseUrl(BASE_URL)
.addConverterFactory(GsonConverterFactory.create())
.build()
.create(WeatherAPI::class.java)
}
}
}
Test file:
class WeatherAPITest {
#Test
fun WeatherAPI_getCurrentWeather_apiKey_city(){
runBlocking {
val res = WeatherAPI.instance.getCurrentWeatherData("123", "London", "no")
assertThat(res.code()).isEqualTo(200)
}
}
}
it fails because there is no token. How can i mock the actual api call to return say 200 on the test and confirm something like this:
assertThat(url).isEqualTo('http:....?key=123&q=London')
assertThat(responseCode).isEqualTo(200)
I'm stuck at Exception that appears in the title. I checked similar topics, but they were specific cases that didn't apply to my situation. Below you could see my models, retrofit setup and its usage. I tried removing Body Class, Response Class, just to check if they are culprits, but unfortunately those weren't the case. Maybe someone will be able to figure out what I'm doing wrong?
Stack Trace:
java.lang.IllegalArgumentException: No Retrofit annotation found. (parameter #2)
for method ApiService.login
at retrofit2.Utils.methodError(Utils.java:52)
at retrofit2.Utils.methodError(Utils.java:42)
at retrofit2.Utils.parameterError(Utils.java:61)
at retrofit2.RequestFactory$Builder.parseParameter(RequestFactory.java:311)
at retrofit2.RequestFactory$Builder.build(RequestFactory.java:182)
at retrofit2.RequestFactory.parseAnnotations(RequestFactory.java:65)
at retrofit2.ServiceMethod.parseAnnotations(ServiceMethod.java:25)
at retrofit2.Retrofit.loadServiceMethod(Retrofit.java:168)
at retrofit2.Retrofit$1.invoke(Retrofit.java:147)
at java.lang.reflect.Proxy.invoke(Proxy.java:1006)
at $Proxy1.login(Unknown Source)
at com.rudearts.cyber2020.services.NetworkService$login$1.invokeSuspend(NetworkService.kt:17)
at com.rudearts.cyber2020.services.NetworkService$login$1.invoke(Unknown Source:10)
at kotlinx.coroutines.flow.SafeFlow.collect(Builders.kt:56)
at kotlinx.coroutines.flow.internal.ChannelFlowOperatorImpl.flowCollect(ChannelFlow.kt:144)
at kotlinx.coroutines.flow.internal.ChannelFlowOperator.collectTo$suspendImpl(ChannelFlow.kt:111)
at kotlinx.coroutines.flow.internal.ChannelFlowOperator.collectTo(Unknown Source:0)
at kotlinx.coroutines.flow.internal.ChannelFlow$collectToFun$1.invokeSuspend(ChannelFlow.kt:33)
at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)
at kotlinx.coroutines.DispatchedTask.run(DispatchedTask.kt:56)
at kotlinx.coroutines.scheduling.CoroutineScheduler.runSafely(CoroutineScheduler.kt:571)
at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.executeTask(CoroutineScheduler.kt:738)
at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.runWorker(CoroutineScheduler.kt:678)
at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.run(CoroutineScheduler.kt:665)
Models:
import com.google.gson.annotations.SerializedName
data class LoginRequest(
#SerializedName("pin") val pin:String,
#SerializedName("pushId") val pushId:String)
import com.google.gson.annotations.SerializedName
data class UserJson (
#SerializedName("id") val id:Long,
#SerializedName("name") val name:String?,
#SerializedName("access_rights") val accessRights:String?)
Retrofit Builder
import okhttp3.OkHttpClient
import retrofit2.Retrofit
import retrofit2.converter.gson.GsonConverterFactory
object RetrofitBuilder {
private const val BASE_URL = "<url>"
private val client = OkHttpClient.Builder().build()
private fun getRetrofit(): Retrofit {
return Retrofit.Builder()
.baseUrl(BASE_URL)
.addConverterFactory(GsonConverterFactory.create())
.client(client)
.build()
}
val apiService: ApiService = getRetrofit().create(ApiService::class.java)
}
import com.rudearts.cyber2020.model.LoginRequest
import com.rudearts.cyber2020.model.UserJson
import retrofit2.Response
import retrofit2.http.Body
import retrofit2.http.Headers
import retrofit2.http.POST
interface ApiService {
#Headers("Content-Type: application/json")
#POST("login.php")
suspend fun login(#Body request: LoginRequest):Response<UserJson>
}
Usage in other class:
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.flow
import java.util.*
class NetworkService {
private val repoService by lazy { RepoService.instance }
private val apiService = RetrofitBuilder.apiService
fun login(pin:String, token:String): Flow<NetworkResult<Boolean>> = flow {
emit(Loading)
try {
val userJson = apiService.login(LoginRequest(pin,token)).body()
userJson?.let {
val user =
User(userJson.id, userJson.name ?: "", emptyList(), userJson.accessRights ?: "")
repoService.user = user
}
emit(NetworkSuccess(userJson != null))
} catch (throwable: Throwable) {
emit(NetworkError(throwable))
}
}
}
It could be that you are using an older version of okhttp / retrofit as you are using suspend function it requires the latest version of both libraries.
Try call the function like this
fun login(LoginRequest(pin,token))
instead of this
fun login(pin:String, token:String)
the exception says the error is on the second parameter
I'm fairly new to Android and Java / Kotlin so I've been struggling to implement cookies in the recommended architecture. I looked in many places, read the documentation and watched many videos and everyone had such different ways to implement things that I was still confused. How does it all fit together?
I would have thought this was such a common use case that I can't believe the answer isn't all over the net, but I've had to work hard to put all the pieces together. Below is what worked for me from the Repository down. I haven't included the database side of things since that is well documented in many places and I found it easy enough to follow (if anyone needs me to include that, let me know). I switched to Kotlin part way through because I could only find some parts of the answer in Java. My example is to log in a user and get basic profile details.
Repository sends login details to server and saves response in database then pulls that info to save as LiveData
package com.example.myapplication
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import com.example.myapplication.*
import com.example.myapplication.asDomainModel
import com.example.myapplication.asDBEntity
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import java.io.IOException
import javax.inject.Inject
class LoginRepository #Inject constructor(
private val myDao: MyDao,
private val myNetwork: Network
) {
private val _profile: MutableLiveData<Profile> = MutableLiveData()
val profile: LiveData<Profile>
get() = _profile
suspend fun login(name: String, password: String) {
withContext(Dispatchers.IO) {
// log in to server and get profile data
val profileNWEntity = myNetwork.login("login", name, password)
// process response
when (profileNWEntity.status) {
"PROFLOGINOK" -> {
// save profile in database then retrieve
myDao.insertProfile(profileNWEntity.asDBEntity())
_profile.postValue(myDao.getProfile(profileNWEntity.user).asDomainModel())
}
else -> {
throw IOException (profileNWEntity.status)
}
}
}
}
}
Retrofit endpoint defines the login process
package com.example.myapplication
import com.example.myapplication.ProfileNWEntity
import retrofit2.http.Field
import retrofit2.http.FormUrlEncoded
import retrofit2.http.POST
interface Network {
#FormUrlEncoded
#POST("server_api")
suspend fun login(
#Field("action") action: String,
#Field("name") name: String,
#Field("pass") password: String
): ProfileNWEntity
}
Entity - used by Gson to parse the network response and by the repository to adapt for the database
package com.example.myapplication
import com.example.myapplication.AccountDBEntity
import com.example.myapplication.ProfileDBEntity
/**
* Base profile response from network query
*/
data class ProfileNWEntity(
val user: Int,
val name: String,
val status: String
)
// map the profile from network to database format
fun ProfileNWEntity.asDBEntity(): ProfileDBEntity {
return ProfileDBEntity(
id = user,
name = name
)
}
Retrofit class to enable inclusion of cookies (together with the interceptors included below, this comes from the work of tsuharesu and Nikhil Jha found at https://gist.github.com/nikhiljha/52d45ca69a8415c6990d2a63f61184ff)
package com.example.myapplication
import android.content.Context
import dagger.hilt.android.qualifiers.ApplicationContext
import okhttp3.OkHttpClient
import retrofit2.Retrofit
import retrofit2.converter.gson.GsonConverterFactory
import javax.inject.Inject
class RetrofitWithCookie #Inject constructor(
context: Context, // uses Hilt to inject the context to be passed to the interceptors
gson: Gson
) {
private val mContext = context
private val gson = gson
fun createRetrofit(): Retrofit {
val client: OkHttpClient
val builder = OkHttpClient.Builder()
builder.addInterceptor(AddCookiesInterceptor(mContext)) // VERY VERY IMPORTANT
builder.addInterceptor(ReceivedCookiesInterceptor(mContext)) // VERY VERY IMPORTANT
client = builder.build()
return Retrofit.Builder()
.baseUrl("myServer URL") // REQUIRED
.client(client) // VERY VERY IMPORTANT
.addConverterFactory(GsonConverterFactory.create(gson))
.build() // REQUIRED
}
}
Receiving Interceptor catches the inbound cookies and saves them in sharedpreferences
package com.example.myapplication
import android.content.Context
import androidx.preference.PreferenceManager
import okhttp3.Interceptor
import okhttp3.Response
import java.io.IOException
import java.util.*
// Original written by tsuharesu
// Adapted to create a "drop it in and watch it work" approach by Nikhil Jha.
// Just add your package statement and drop it in the folder with all your other classes.
class ReceivedCookiesInterceptor(context: Context?) : Interceptor {
private val context: Context?
#Throws(IOException::class)
override fun intercept(chain: Interceptor.Chain): Response {
val originalResponse = chain.proceed(chain.request())
if (!originalResponse.headers("Set-Cookie").isEmpty()) {
val cookies = PreferenceManager.getDefaultSharedPreferences(context)
.getStringSet("PREF_COOKIES", HashSet()) as HashSet<String>?
for (header in originalResponse.headers("Set-Cookie")) {
cookies!!.add(header)
}
val memes = PreferenceManager.getDefaultSharedPreferences(context).edit()
memes.putStringSet("PREF_COOKIES", cookies).apply()
memes.commit()
}
return originalResponse
}
init {
this.context = context
} // AddCookiesInterceptor()
}
AddCookies interceptor adds the cookie back into future requests
package com.example.myapplication
import android.content.Context
import androidx.preference.PreferenceManager
import dagger.hilt.android.qualifiers.ActivityContext
import okhttp3.Interceptor
import okhttp3.Response
import timber.log.Timber
import java.io.IOException
import java.util.*
// Original written by tsuharesu
// Adapted to create a "drop it in and watch it work" approach by Nikhil Jha.
// Just add your package statement and drop it in the folder with all your other classes.
/**
* This interceptor put all the Cookies in Preferences in the Request.
* Your implementation on how to get the Preferences may ary, but this will work 99% of the time.
*/
class AddCookiesInterceptor(#ActivityContext context: Context?) : Interceptor {
// We're storing our stuff in a database made just for cookies called PREF_COOKIES.
// I reccomend you do this, and don't change this default value.
private val context: Context?
#Throws(IOException::class)
override fun intercept(chain: Interceptor.Chain): Response {
val builder = chain.request().newBuilder()
val preferences = PreferenceManager.getDefaultSharedPreferences(context).getStringSet(
PREF_COOKIES, HashSet()
) as HashSet<String>?
// Use the following if you need everything in one line.
// Some APIs die if you do it differently.
/*String cookiestring = "";
for (String cookie : preferences) {
String[] parser = cookie.split(";");
cookiestring = cookiestring + parser[0] + "; ";
}
builder.addHeader("Cookie", cookiestring);
*/for (cookie in preferences!!) {
builder.addHeader("Cookie", cookie)
Timber.d("adding cookie %s", cookie)
}
return chain.proceed(builder.build())
}
companion object {
const val PREF_COOKIES = "PREF_COOKIES"
}
init {
this.context = context
}
}
Hilt Module to tie it all together
package com.example.myapplication
import android.content.Context
import com.example.myapplication.Network
import com.google.gson.Gson
import com.google.gson.GsonBuilder
import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
import dagger.hilt.android.qualifiers.ApplicationContext
import dagger.hilt.components.SingletonComponent
import retrofit2.Retrofit
import retrofit2.converter.gson.GsonConverterFactory
import javax.inject.Singleton
#InstallIn(SingletonComponent::class)
#Module
class NetworkModule {
#Singleton
#Provides
fun provideNetwork(retrofit: Retrofit)
: Network = retrofit.create(Network::class.java)
#Singleton
#Provides
fun provideRetrofitWithCookie(
#ApplicationContext context: Context,
gson: Gson
): Retrofit = RetrofitWithCookie(context, gson).createRetrofit()
#Singleton
#Provides
fun provideGson(): Gson = GsonBuilder()
.setDateFormat("yyyy-MM-dd'T'HH:mm:ssZ") // used for parsing other responses
.create()
}
I am trying to retrieve several rows from an API using Retrofit and Moshi, but am facing this error:
Retrofit error:- Expected BEGIN_ARRAY but was BEGIN_OBJECT at path$
The API endpoint I am requesting the data from is:
https://thecodecafe.in/gogrocer-ver2.0/api/top_selling
This is the setup code for Retrofit and Moshi that I am using to request the data from the API:
import com.squareup.moshi.Moshi
import com.squareup.moshi.kotlin.reflect.KotlinJsonAdapterFactory
import retrofit2.Call
import retrofit2.Retrofit
import retrofit2.converter.moshi.MoshiConverterFactory
import retrofit2.http.GET
private const val BASE_URL = "https://thecodecafe.in/gogrocer-ver2.0/api/"
val moshi = Moshi.Builder()
.add(KotlinJsonAdapterFactory())
.build()
val retrofit = Retrofit.Builder()
.addConverterFactory(MoshiConverterFactory.create(moshi))
.baseUrl(BASE_URL)
.build()
interface GroceryApiServices {
#GET("top_selling")
fun getProperties():
Call<List<GroceryProperty>>
}
object GroceryApi {
val retrofitServices: GroceryApiServices by lazy { retrofit.create(GroceryApiServices::class.java)}
}
This is the logic of my view model class, showing how I want to retrieve the data:
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import com.kotlin_developer.grocerysell.network.GroceryApi
import com.kotlin_developer.grocerysell.network.GroceryProperty
import retrofit2.Call
import retrofit2.Callback
import retrofit2.Response
class OverviewViewModel: ViewModel() {
private val _response = MutableLiveData<String>()
val response: LiveData<String>
get() = _response
init {
getGroceryProperties()
}
private fun getGroceryProperties(){
GroceryApi.retrofitServices.getProperties().enqueue(object : Callback<List<GroceryProperty>>{
override fun onFailure(call: Call<List<GroceryProperty>>, t: Throwable) {
_response.value = t.message
}
override fun onResponse(
call: Call<List<GroceryProperty>>,
response: Response<List<GroceryProperty>>
) {
_response.value="Success ${response.body()?.size} Grocery Property arrived"
}
})
}
override fun onCleared() {
super.onCleared()
}
}
This is how I want to retrieve data in my view model class
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import com.kotlin_developer.grocerysell.network.GroceryApi
import com.kotlin_developer.grocerysell.network.GroceryProperty
import retrofit2.Call
import retrofit2.Callback
import retrofit2.Response
class OverviewViewModel: ViewModel() {
private val _response = MutableLiveData<String>()
val response: LiveData<String>
get() = _response
init {
getGroceryProperties()
}
private fun getGroceryProperties(){
GroceryApi.retrofitServices.getProperties().enqueue(object : Callback<List<GroceryProperty>>{
override fun onFailure(call: Call<List<GroceryProperty>>, t: Throwable) {
_response.value = t.message
}
override fun onResponse(
call: Call<List<GroceryProperty>>,
response: Response<List<GroceryProperty>>
) {
_response.value="Success ${response.body()?.size} Grocery Property arrived"
}
})
}
override fun onCleared() {
super.onCleared()
}
}
The error you getting is Moshi telling you that it expects a JSON array, but it got an object. Your Retrofit endpoint method looks like this:
#GET("top_selling")
fun getProperties():
Call<List<GroceryProperty>>
Here, you are telling Retrofit that you expect a List. In JSON, this would be the array Moshi is expecting. However, upon clicking on the link to the endpoint you provided, the JSON you are receiving looks like this:
{
"status": "1",
"message": "top selling products",
"data": [
...
]
}
As you can see, this JSON is not an array but an object that contains an array, and that's where Moshi's error originates. To deserialize it into a List, it expected the begin of an array ([), but what it found was actually the begin of an object ({)
To sum it up, you are not expecting a List, but an object, which in turn contains that List (the data array in the JSON).
You would need to define another class that encapsulates this List, something the likes of this:
data class TopSellingResponse(
val status: String,
val message: String,
val data: List<GroceryProperty>
)
If you then change your method signature to
#GET("top_selling")
fun getProperties():
Call<TopSellingResponse>
Moshi should be able to deserialize the JSON object into your class and the contained data array into the List you initially expected.
I'm using Imgur API to fetch data, but this API needs authentication. I have used a retrofit library and I have client id for API. I'm very new and I don't have any idea how to add authentication using a retrofit. Please help me. Here is a tutorial but they are not using a retrofit Tutorial.
ApiClient.kt
package com.example.imgurapi.Network
import okhttp3.*
import retrofit2.Retrofit
import retrofit2.converter.gson.GsonConverterFactory
import java.io.IOException
object ApiClient {
private var BASE_URL: String = "https://api.imgur.com/3/image/"
private var apiClient2: ApiClient? = null
private var retrofit: Retrofit? = null
init {
val clientBuilder = OkHttpClient.Builder()
//Create a new Interceptor.
//Create a new Interceptor.
val headerAuthorizationInterceptor: Interceptor = object : Interceptor {
#Throws(IOException::class)
override fun intercept(chain: Interceptor.Chain): Response? {
var request: Request = chain.request()
val headers: Headers =
request.headers().newBuilder().add("Authorization", "01016a9fff88608").build()
request = request.newBuilder().headers(headers).build()
return chain.proceed(request)
}
}
clientBuilder.addInterceptor(headerAuthorizationInterceptor)
retrofit = Retrofit.Builder().baseUrl(BASE_URL)
.addConverterFactory(GsonConverterFactory.create()).build()
}
fun getInstance(): ApiClient? {
if (apiClient2 == null) {
apiClient2 = ApiClient
}
return apiClient2
}
fun getApi(): ApiInterface? {
return retrofit?.create(ApiInterface::class.java)
}
ApiTerface.kt
package com.example.imgurapi.Network
import ImageData
import retrofit2.Call
import retrofit2.http.GET
import retrofit2.http.Path
interface ApiInterface {
#GET("image/{id}")
open fun imageById(#Path("id") id: String?): Call<ImageData?>?
}
Each client must register their application and receive the client_id and client_secret.
For public read-only and anonymous resources, such as getting image info, looking up user comments, etc. all you need to do is send an authorization header with your client_id in your requests. This also works if you'd like to upload images anonymously (without the image being tied to an account), or if you'd like to create an anonymous album. This lets us know which application is accessing the API.
Authorization: Client-ID YOUR_CLIENT_ID
For more info :
Flow the step by step instructions in
https://apidocs.imgur.com/?version=latest