Retrofit 2 request always failure - Kotlin - android

I tried to learn about kotlin and retrofit 2 at the same time. I have this code.
I want to take all the /posts from this. But its always return a failure code. I'm very new in this, thanks
Network interface
interface APIService {
#GET("/posts")
fun getPosts(): Call<List<UserData>>
POJO class
open class UserData {
#SerializedName("userId")
#Expose
open var user_id: Int? = null
#SerializedName("id")
#Expose
open var id: Int? = null
#SerializedName("title")
#Expose
open var title: String? = null
#SerializedName("body")
#Expose
open var body: String? = null
}
MainActivity
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val retrofit = Retrofit.Builder()
.addConverterFactory(GsonConverterFactory.create())
.baseUrl("https://jsonplaceholder.typicode.com/")
.build()
val service = retrofit.create(APIService::class.java)
service.getPosts().enqueue(object : Callback<List<UserData>> {
override fun onFailure(call: Call<List<UserData>>?, t: Throwable?) {
Log.d("RetrofitTest", t.toString())
}
override fun onResponse(call: Call<List<UserData>>?, response: Response<List<UserData>>?) {
Log.d("RetrofitTest", "onFailure")
}
})
}
}

in APIService interface #GET("posts") insted of #GET("/posts")

It always failed because the reason is the invalid URL. The mistake you have done is '/'. Either you can put the slash('/') in the base URL or at the starting of the Endpoint. In your case, the URL is like "https://jsonplaceholder.typicode.com//posts". Which is invalid that's why your request getting failed. So just remove the '/' from the Endpoint.
Make request interface like this:
interface APIService {
#GET("posts")
fun getPosts(): Call<List<UserData>>
}

Related

Retrofit Kotlin unable to create a working post request

I'm trying to do a POST request using Retrofit but I'm unable to make it work. It does work on Postman. I specified the header "Content-Type: application/json" and set my "email" and "password" parameters in the body and it works well.
But it doesn't on Android. Here are my codes :
private fun login() {
val user = User("test#gmail.com", "dsea2EcFI32\\\"af'xn")
this.service.login(user).enqueue(object : Callback<LoginResponse> {
override fun onResponse(call: Call<LoginResponse>, response: Response<LoginResponse>) {
if (response.code() == 200) {
// TODO
}
}
override fun onFailure(call: Call<LoginResponse>, t: Throwable) {
// TODO
println(t.message)
}
})
}
The request :
#Headers("Content-Type: application/json")
#POST("/api/authentication/login")
fun login(#Body body: User): Call<LoginResponse>
User model
data class User(val email: String, val password: String)
LoginResponse :
class LoginResponse {
#SerializedName("user")
val user : UserResponse? = null
}
class UserResponse {
#SerializedName("id") val still : String = null
#SerializedName("firstName") val running : String = null
#SerializedName("lastName") val bicycle : String = null
#SerializedName("email") val walking : String = null
#SerializedName("token") val vehicle : String = null
}
In case the auth is a failure, the server sends me back an HTML page so the only error I have is
Use JsonReader.setLenient(true) to accept malformed JSON at line 1 column 1 path $
I already set it to true and it keeps saying me that the GSON parsed object isn't a JSON object but I know there's an Android code here
Can someone helps me finding it ?
PS : I even tried to send the body as a JSON object but same error
PS2 : might this be due to the password even If I added enough backspace to accept the special characters ? the real string is dsea2EcFI32"af'xn
EDIT :
As asked, here is my retrofit builder with the HTTPInterceptor
val client = OkHttpClient()
val interceptor = HttpLoggingInterceptor()
interceptor.level = HttpLoggingInterceptor.Level.BODY
client.interceptors().add(interceptor)
val retrofit = Retrofit.Builder()
.baseUrl(BuildConfig.API_URL)
.addConverterFactory(GsonConverterFactory.create())
.client(client)
.build()
this.service = retrofit.create(LoginResponse::class.java)
I found the solution.
The issue was the password because it had backslashes and quotes inside of it.
Kotlin was doing a wrong parsing.
Convert your fun login object like below one.
#Headers("Content-Type: application/json")
#POST("/api/authentication/login")
fun login(#Body requestBody: RequestBody): Call<LoginResponse>
then create a fun like this
fun makeGSONRequestBody(jsonObject: Any?): RequestBody {
return RequestBody.create(MediaType.parse("multipart/form-data"), Gson().toJson(jsonObject))
}
you need to pass your User object like below
private fun login() {
val user = User("test#gmail.com", "dsea2EcFI32\\\"af'xn")
this.service.login(makeGSONRequestBody(user)).enqueue(object : Callback<LoginResponse> {
override fun onResponse(call: Call<LoginResponse>, response: Response<LoginResponse>) {
if (response.code() == 200) {
// TODO
}
}
override fun onFailure(call: Call<LoginResponse>, t: Throwable) {
// TODO
println(t.message)
}
})
}

Kotlin Retrofit Error: "Expected BEGIN_OBJECT but was string"

I am working on an android project. I use Kotlin and I'm just learning it. I want to use Retrofit 2 for my request. I use Java this method and it is done.
I want to register user my database. When I use my function, return success for web services but it saves the empty value and I take this error: "Expected BEGIN_OBJECT was string at line 3 column 1 path$". How we can solve this problem? Actually, I read and implementing other solutions but they don't work for me.
My API:
#POST("userregister.php")
fun doRegister(
#Body signupRequest: SignupRequest
): Call<SignupResponse> // body data
My API Service:
object ApiServiceWithOutRX {
private const val BASE_URL = "https://alperenyukselaltug.com/api/TurkAi/"
var gson = GsonBuilder()
.setLenient()
.create()
fun ApiCall() = Retrofit.Builder()
.baseUrl(BASE_URL)
.addConverterFactory(ScalarsConverterFactory.create())
.addConverterFactory(GsonConverterFactory.create(gson))
.client(ApiWorker.client)
.build()
.create(APIListWithOutRX::class.java)!!
}
And my register function:
val email = editTextMail!!.text.toString().trim()
val password = editTextPassword!!.text.toString().trim()
val json = JSONObject()
json.put("UserEmail", email)
json.put("UserPassword", password)
json.put("UserProfilePicture", "")
ApiServiceWithOutRX.ApiCall().doRegister(
SignupRequest(
email,
password,
""
)
).enqueue(object : Callback<SignupResponse> {
override fun onResponse(
call: Call<SignupResponse>,
response: Response<SignupResponse>
) {
Log.d("Response::::", response.body().toString())
val loginResponse: SignupResponse
loginResponse = response.body()!!
if (loginResponse.status) {
finish()
} else {
Toast.makeText(applicationContext, response.body()!!.message, Toast.LENGTH_LONG)
.show()
}
}
override fun onFailure(call: Call<SignupResponse>, t: Throwable) {
Toast.makeText(applicationContext, t.message, Toast.LENGTH_LONG).show()
}
})
My Model:
data class User(
#SerializedName("UserEmail")
val UserEmail: String?,
#SerializedName("UserPassword")
val UserPassword: String?,
#SerializedName("UserProfilePicture")
val UserProfilePicture: String?
)
class SignupResponse(val status: Boolean, val message:String, val data: User)
class SignupRequest(#SerializedName("UserEmail") var UserEmail: String,
#SerializedName("UserPassword") var UserPassword: String,
#SerializedName("UserProfilePicture") var UserProfilePicture: String)
I solve this problem. First, I return my server side a string with PHP in echo. I return a string value. So I change my #Post like this:
#FormUrlEncoded
#POST("userregister.php")
fun doRegister(
#Field("UserEmail") UserEmail:String,
#Field("UserPassword") UserPassword:String,
#Field("UserProfilePicture") UserProfilePicture:String
):Call<String>

Can't getting data from API using retrofit2

help me for this issue please, I want to get data from API
{
"status": true,
"data": [
{
"id_pelanggan": "456",
"nama_pelanggan": "ahmad",
"alamat": "taliwang"
},
{
"id_pelanggan": "457",
"nama_pelanggan": "ahmad",
"alamat": "taliwang"
}
]}
this is my API object for setup for dynamic class, retrofit2 and gson
object Api {
private val BASE_URL: String = BuildConfig.API_SRAPP
private var gson = GsonBuilder().setLenient().create()
private val httpClient = OkHttpClient.Builder()
fun <T>service(java: Class<T>): T{
val retrofit = Retrofit.Builder()
.addConverterFactory(ScalarsConverterFactory.create())
.addConverterFactory(GsonConverterFactory.create(gson))
.baseUrl(BASE_URL)
.client(httpClient.build())
.build()
return retrofit.create(java)
}
}
this is my class BaseRespon for handle all data from API and with dynamic class
data class BaseResponse<T>(
val status: Boolean,
val data: T?
)
this is my class Customer
data class Customer (
#SerializedName("id_pelanggan")
#Expose
val idPelanggan: String,
#SerializedName("nama_pelanggan")
#Expose
val namaPelanggan: String,
#SerializedName("alamat")
#Expose
val alamat: String
)
this is my API service
interface CustomerServices {
#GET("customer")
fun getAllCustomer(#Header("Authorization") auth: String): Call<BaseResponse<ArrayList<Customer>>>
#GET("customer")
fun getCustomerbyID(#Header("Authorization") auth: String, #Query("id") id: String): Call<BaseResponse<Customer>>
}
and this is my class for using API
class CustomerPresenter {
fun loadAllCustomer(){
apiCustomer.getAllCustomer(OfflineHelper.getToken())
.enqueue(object : Callback<BaseResponse<ArrayList<Customer>>>{
override fun onFailure(call: Call<BaseResponse<ArrayList<Customer>>>, t: Throwable) {
Log.e("allCustomer", "${t.message}")
}
override fun onResponse(
call: Call<BaseResponse<ArrayList<Customer>>>,
response: Response<BaseResponse<ArrayList<Customer>>>
) {
saveLocalCustomer(response.body())
}
})
}
fun saveLocalCustomer(data: BaseResponse<ArrayList<Customer>>?){
Log.w("loadedAll", "${data?.status}")
}
and I have log failure in loadAllCustomer
java.lang.IllegalStateException: Expected BEGIN_OBJECT but was STRING at line 2 column 1 path $
I don't understand why it's failure, my endpoint is not typo, and I have tested it on postman
You need to use a List as your return type for the data field. try this:
data class BaseResponseList<T>(
val status: Boolean,
val data: List<T>?
)
and then using BaseResponseList in your call as following:
#GET("customer")
fun getAllCustomer(#Header("Authorization") auth: String): Call<BaseResponseList<Customer>>
Use BaseResponseList for any endpoint that returns a List, and BaseResponse for Objects
Update your response class and api interface like below
data class BaseResponse(
val status: Boolean,
val data: List<Customer>?
)
interface CustomerServices {
#GET("customer")
fun getAllCustomer(#Header("Authorization") auth: String): Call<BaseResponse>
#GET("customer")
fun getCustomerbyID(#Header("Authorization") auth: String, #Query("id") id: String): Call<Customer>
}
your model not same with you object json

How to test api requests and fill classes with fake data?

I can not find a solution to this problem on the Internet. Or is my code so bad?
Interface
interface GetWeatherService {
#GET("/data/2.5/forecast?")
fun getAllWeather(#Query("q")cityName: String, #Query("APPID")app_id: String, #Query("units")units: String="imperial"): Call<ListWeather>
#GET("/data/2.5/weather?")
fun getCurrentWeather(#Query("q")cityName: String, #Query("APPID")app_id: String, #Query("units")units: String="imperial"): Call<MainWeather>
#GET("/data/2.5/weather?")
fun getWeatherDataFromLocation(#Query("lat")lat: String, #Query("lon")lon: String, #Query("APPID") app_id: String): Call<DataFromLocation>
}
Client
object RetrofitClientInstance {
private var retrofit: Retrofit? = null
private var BASE_URL = "https://api.openweathermap.org/"
val retrofitInstance: Retrofit?
get(){
if(retrofit == null){
retrofit = retrofit2.Retrofit.Builder()
.baseUrl(BASE_URL)
.addConverterFactory(GsonConverterFactory.create())
.build()
}
return retrofit
}
}
MainActivity()
class MainActivity: AppCompatActivity {
fun getDataFromApi(cityName: String) {
val service = RetrofitClientInstance.retrofitInstance!!.create(GetWeatherService::class.java)
// Service 1
service.getCurrentWeather(cityName, APP_ID).enqueue(object: Callback<MainWeather>{
override fun onFailure(call: Call<MainWeather>, t: Throwable) {
}
override fun onResponse(call: Call<MainWeather>, response: Response<MainWeather>) {
val weather = response.body()
val currentWeather = CurrentWeather(
weather!!.main.temp,
weather.weather[0].description,
weather.name,
weather.weather[0].main
)
updateCurrentWeatherUI(currentWeather)
}
})
service.getAllWeather(cityName, APP_ID).enqueue(object: Callback<ListWeather>{
override fun onFailure(call: Call<ListWeather>, t: Throwable) {
}
override fun onResponse(call: Call<ListWeather>, response: Response<ListWeather>) {
val weather = response.body()!!.list
for(item in weather){
val weatherList = NextFiveDayWeather(
item.dt,
item.main.temp,
item.weather[0].description
)
weatherArray.add(weatherList)
updateUI(weatherArray)
}
}
})
}
}
Data class
data class MainWeather (
var dt: Long,
var main: MainDTO,
var weather: List<WeatherDTO>,
var dt_txt: String,
var name: String
)
data class WeatherDTO (var main: String, var description: String, var icon: String)
data class ListWeather (#SerializedName("list") var list: List<MainWeather>)
I can’t test the request to api and I can’t fill the classes with fake data?
Tell me please, what should I do?
What should I study? And if you are not difficult can give advice for the future?
You can download the software Postman to test api endpoints: https://www.getpostman.com/

Empty response body on retrofit call

I have a app whos call a service POST (postman test) and get some information from that call.
My retrofit initializer:
class RetrofitInitializer {
private val retrofit = Retrofit.Builder()
.baseUrl("https://bank-app-test.herokuapp.com/api/")
.addConverterFactory(GsonConverterFactory.create())
.build()
fun loginApiContract() : LoginApiContract{
return retrofit.create(LoginApiContract::class.java)
}
}
the interface:
interface LoginApiContract {
#POST("login")
fun login() : Call<UserAccount>
#GET("statements")
fun getStatements()
}
and finally the call:
val call = RetrofitInitializer().loginApiContract().login()
call.enqueue(object: Callback<UserAccount> {
override fun onResponse(call: Call<UserAccount?>?,
response: Response<UserAccount?>?) {
response?.body()?.let {
val myUserAccount: UserAccount = it
loginView.doLogin(myUserAccount)
}
}
override fun onFailure(call: Call<UserAccount?>?,
t: Throwable?) {
Log.e("onFailure error", t?.message)
}
})
I got response code 200, but response body is empty.
This is my postman response:
{
"userAccount": {
"userId": 1,
"name": "Jose da Silva Teste",
"bankAccount": "2050",
"agency": "012314564",
"balance": 3.3445
},
"error": {}
}
and this is my model:
class UserAccount constructor(var userId: Int, var name: String, var bankAccount: String, var agency: String, var balance: Double){
init{
this.userId = userId
this.name = name
this.bankAccount = bankAccount
this.agency = agency
this.balance = balance
}
}
I found!
Its a problem with my postman. For any reason my AVD cant access the mock server. now i solve my problem with a simple restful api node.js.
Thx for the help guys.
Your response contains elements named "userAccount" and "error". Your UserAccount class has neither which is causing the issue. Therefore, use Retrofit with a class like this:
data class UserResponse(val userAccount: UserAccount, val error: BackendError)

Categories

Resources