Retrofit Call.enqueue method not being called - kotlin - android

i was able to make Edit and Delete work in a test project, using Retrofit2, but now i needed to used it in my real project, and despite the code and webservice used (created using Slim 4 and Notorm) are the same, both Edit and Delete started not working for some reason (get and Post both work correctly)
For both Edit i am using a page that get all the date from the DB and put it in some views, and then i click in a button that have the Edit Function . I tried debugging and everything is fine, until it tries to enter the call.enqueue method, and it fails, without giving any error on my logcat. I have a toast on my onResponse and another toast on my OnFailure and neither shows on my screen.
With the Delete the situation is similar i have a page, with a call.enqueue (on the on.create) that gets all the data that appears in the views, and then a button that trigger the delete method, and here also the call.enqueue doens't work. I tested this, without having the call.enqueue on the oncreate, but it happens the same thing.
Here is my Edit button code
fun editar(view: View) {
val request = ServiceBuilder.buildService(EndPoints::class.java)
val latitude = latitude.text.toString().toDouble()
val longitude = longitude.text.toString().toDouble()
val morada = editMoradaView.text.toString()
val n_quartos = editNQuartosView.text.toString().toInt()
val casaBanho = casaBanho.text.toString().toInt()
val contacto = contacto.text.toString()
val mobilada = mobilada.text.toString()
val preco = preco.text.toString().toDouble()
val observacao = observacao.text.toString()
val utilizador_id = shared_preferences.getInt("id", 0)
if (isBitMap) {
val base = getBase64String(decodedByte)
fotografia = base
} else {
fotografia = base64
}
val call = request.editar(
id = ID,
users_id = utilizador_id,
morada = morada,
n_quartos = n_quartos,
latitude = latitude.toString().toDouble(),
longitude = longitude.toString().toDouble(),
fotografia = fotografia,
preco = preco,
ncasas_banho = casaBanho,
telemovel = contacto,
mobilado = mobilada,
outros_atributos = observacao
)
call.enqueue(object : Callback<OutputEditar> {
override fun onResponse(call: Call<OutputEditar>, response: Response<OutputEditar>) {
if (response.isSuccessful) {
val c: OutputEditar = response.body()!!
Toast.makeText(this#EditarAnuncios, c.MSG, Toast.LENGTH_LONG).show()
val intent = Intent(this#EditarAnuncios, MapsActivity::class.java)
startActivity(intent)
finish()
}
}
override fun onFailure(call: Call<OutputEditar>, t: Throwable) {
Toast.makeText(this#EditarAnuncios, "${t.message}", Toast.LENGTH_SHORT).show()
}
})
}
Here is my Delete Button Code
fun delete(view: View) {
var id = intent.getStringExtra("PARAM_ID")
var idString = id.toString()
val request = ServiceBuilder.buildService(EndPoints::class.java)
val call = request.apagarAnuncio(id = idString.toInt())
call.enqueue(object : Callback<OutputApagar> {
override fun onResponse(call: Call<OutputApagar>, response: Response<OutputApagar>) {
if (response.isSuccessful) {
val c: OutputApagar = response.body()!!
Toast.makeText(this#DetalhesAnuncioLogado, c.MSG, Toast.LENGTH_LONG).show()
val intent = Intent(this#DetalhesAnuncioLogado, MapsActivity::class.java)
setResult(Activity.RESULT_OK, intent)
finish()
}
}
override fun onFailure(call: Call<OutputApagar>, t: Throwable) {
Toast.makeText(this#DetalhesAnuncioLogado, "${t.message}", Toast.LENGTH_SHORT)
.show()
}
})
}
Here are my 2 data classes (in separated files)
data class OutputEditar(
val users_id: Int,
val morada: String,
val n_quartos: Int,
val latitude: Double,
val longitude: Double,
val fotografia: String,
val preco: Double,
val ncasas_banho: Int,
val telemovel: String,
val mobilado: String,
val outros_atributos: String,
val status: String,
val MSG: String
)
data class OutputApagar(
val status: String,
val MSG: String
)
Here are my endpoints
#FormUrlEncoded
#POST("/editar_anuncios/{id}")
fun editar(#Path("id") id: Int?,
#Field("users_id") users_id: Int?,
#Field("morada") morada: String?,
#Field("n_quartos") n_quartos: Int?,
#Field("latitude") latitude: Double?,
#Field("longitude") longitude: Double?,
#Field("fotografia") fotografia: String?,
#Field("preco") preco: Double?,
#Field("ncasas_banho") ncasas_banho: Int?,
#Field("telemovel") telemovel: String?,
#Field("mobilado") mobilado: String?,
#Field("outros_atributos") outros_atributos: String?): Call<OutputEditar>
#POST("/apagarAnuncios/{id}")
fun apagarAnuncio(#Path("id") id: Int?): Call<OutputApagar>
Both Endpoints work well on my Test Project and on Postman
My Service Builder
object ServiceBuilder {
private val client = OkHttpClient.Builder().build()
private val retrofit = Retrofit.Builder()
.baseUrl("https://tneveda.000webhostapp.com/")
.addConverterFactory(GsonConverterFactory.create())
.client(client)
.build()
fun<T> buildService(service: Class<T>): T {
return retrofit.create(service)
}
}
Thank You in advance

That moment, when 10 minutes i made this Question, i found out my problem. Forgot to update my Endpoints on my Real Project, and that is why it was not working

Related

What is the right way to post with retrofit 2 and moshi

I've been trying to make a POST with Retrofit 2 and Moshi but I've been unable to get it to work.
My data classes look like this:
data class Order(
val items: List<Item>?,
val customerName: String,
val customerPhoneNo: String,
val customerAddress: String,
val note: String
)
data class Item(
val productUid: String,
var quantity: Int
)
The interface looks like this
interface ProductService {
#POST("/api/order/saveorder")
suspend fun postProds(
#Field("customerName") customerName: String,
#Field("customerPhoneNo") customerPhone: String,
#Field("customerAddress") address: String,
#Field("note") customerNote:String,
#Field("items") orderItems: List<Item>
): Response<Order>
#GET("/api/product/allproducts")
suspend fun getProds(): Response<List<ProdsItem>>
}
private val moshi = Moshi.Builder()
.add(KotlinJsonAdapterFactory())
.build()
object Network {
private val retrofit = Retrofit.Builder()
.baseUrl(BASE_URL)
.addConverterFactory(MoshiConverterFactory.create(moshi)
.asLenient()
)
.build()
object ProdsApi {
val retrofitService: ProductService by lazy {
retrofit.create(ProductService::class.java)
}
}
}
The postProds fun is called like this:
suspend fun sendOrder(order: Order) {
withContext(Dispatchers.Main){
try {
val orderResponse = Network.ProdsApi.retrofitService.postProds(
order.customerName,
order.customerPhoneNo,
order.customerAddress,
order.note,
order.items )
}
catch (e: Exception) {
Timber.e(e)
}
}
}
Trying to POST this way keeps yielding this response:
Response{protocol=h2, code=400, message=, url=
However, I tried converting the Order object to json directly in my viewModel as follows:
val moshi: Moshi = Moshi.Builder().add(KotlinJsonAdapterFactory()).build()
val jsonAdapter: JsonAdapter<Order> = moshi.adapter(Order::class.java)
val jsonString = jsonAdapter.toJson(customerOrder)
Timber.d(jsonString)
I then tested the generated jsonString on Postman and got a 200 response.
I need some help figuring out what I'm doing wrong in my code, please.
In postman, you are sending data in the body of the request. But in your code, it is going as key-value params. Try to send it in the body from your code. Try below snippet.
Update your Order Data class:
#JsonClass(generateAdapter = true)
data class Order(
#Json(name = "items")
val items: List<Item>?,
#Json(name = "customerName")
val customerName: String,
#Json(name = "customerPhoneNo")
val customerPhoneNo: String,
#Json(name = "customerAddress")
val customerAddress: String,
#Json(name = "note")
val note: String
)
#JsonClass(generateAdapter = true)
data class Item(
#Json(name = "productUid")
val productUid: String,
#Json(name = "quantity")
var quantity: Int
)
Now the ProductService Interface:
interface ProductService {
#POST("/api/order/saveorder")
suspend fun postProds(
#Body request: Order
): Response<Order>
#GET("/api/product/allproducts")
suspend fun getProds(): Response<List<ProdsItem>>
}
Now Pass the request object in your function call:
suspend fun sendOrder(order: Order) {
withContext(Dispatchers.Main){
try {
val orderResponse = Network.ProdsApi.retrofitService.postProds(order)
}
catch (e: Exception) {
Timber.e(e)
}
}
}

Unable to invoke no-args constructor for retrofit2.Call MVVM Coroutines Retrofit

I want to use coroutines in my project only when I use coroutines I get the error :Unable to invoke no-args constructor. I don't know why it's given this error. I am also new to coroutines.
here is my apiclient class:
class ApiClient {
val retro = Retrofit.Builder()
.baseUrl(Constants.BASE_URL)
.addConverterFactory(GsonConverterFactory.create())
.build()
}
Here is my endpoint class:
#GET("v2/venues/search")
suspend fun get(
#Query("near") city: String,
#Query("limit") limit: String = Constants.limit,
#Query("radius") radius: String = Constants.radius,
#Query("client_id") id: String = Constants.clientId,
#Query("client_secret") secret: String = Constants.clientSecret,
#Query("v") date: String
): Call<VenuesMainResponse>
my Repository class:
class VenuesRepository() {
private val _data: MutableLiveData<VenuesMainResponse?> = MutableLiveData(null)
val data: LiveData<VenuesMainResponse?> get() = _data
suspend fun fetch(city: String, date: String) {
val retrofit = ApiClient()
val api = retrofit.retro.create(VenuesEndpoint::class.java)
api.get(
city = city,
date = date
).enqueue(object : Callback<VenuesMainResponse>{
override fun onResponse(call: Call<VenuesMainResponse>, response: Response<VenuesMainResponse>) {
val res = response.body()
if (response.code() == 200 && res != null) {
_data.value = res
} else {
_data.value = null
}
}
override fun onFailure(call: Call<VenuesMainResponse>, t: Throwable) {
_data.value = null
}
})
}
}
my ViewModel class:
class VenueViewModel( ) : ViewModel() {
private val repository = VenuesRepository()
fun getData(city: String, date: String): LiveData<VenuesMainResponse?> {
viewModelScope.launch {
try {
repository.fetch(city, date)
} catch (e: Exception) {
Log.d("Hallo", "Exception: " + e.message)
}
}
return repository.data
}
}
part of activity class:
class MainActivity : AppCompatActivity(){
private lateinit var venuesViewModel: VenueViewModel
private lateinit var adapter: HomeAdapter
private var searchData: List<Venue>? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val editText = findViewById<EditText>(R.id.main_search)
venuesViewModel = ViewModelProvider(this)[VenueViewModel::class.java]
venuesViewModel.getData(
city = "",
date = ""
).observe(this, Observer {
it?.let { res ->
initAdapter()
rv_home.visibility = View.VISIBLE
adapter.setData(it.response.venues)
searchData = it.response.venues
println(it.response.venues)
}
})
this is my VenuesMainResponse data class
data class VenuesMainResponse(
val response: VenuesResponse
)
I think the no-args constructor warning should be related to your VenuesMainResponse, is it a data class? You should add the code for it as well and the complete Log details
Also, with Coroutines you should the change return value of the get() from Call<VenuesMainResponse> to VenuesMainResponse. You can then use a try-catch block to get the value instead of using enqueue on the Call.
Check this answer for knowing about it and feel free to ask if this doesn't solve the issue yet :)
UPDATE
Ok so I just noticed that it seems that you are trying to use the foursquare API. I recently helped out someone on StackOverFlow with the foursquare API so I kinda recognize those Query parameters and the Venue response in the code you provided above.
I guided the person on how to fetch the Venues from the Response using the MVVM architecture as well. You can find the complete code for getting the response after the UPDATE block in the answer here.
This answer by me has code with detailed explanation for ViewModel, Repository, MainActivity, and all the Model classes that you will need for fetching Venues from the foursquare API.
Let me know if you are unable to understand it, I'll help you out! :)
RE: UPDATE
So here is the change that will allow you to use this code with Coroutines as well.
Repository.kt
class Repository {
private val _data: MutableLiveData<mainResponse?> = MutableLiveData(null)
val data: LiveData<mainResponse?> get() = _data
suspend fun fetch(longlat: String, date: String) {
val retrofit = Retro()
val api = retrofit.retro.create(api::class.java)
try {
val response = api.get(
longLat = longlat,
date = date
)
_data.value = response
} catch (e: Exception) {
_data.value = null
}
}
}
ViewModel.kt
class ViewModel : ViewModel() {
private val repository = Repository()
val data: LiveData<mainResponse?> = repository.data
fun getData(longLat: String, date: String) {
viewModelScope.launch {
repository.fetch(longLat, date)
}
}
}
api.kt
interface api {
#GET("v2/venues/search")
suspend fun get(
#Query("ll") longLat: String,
#Query("client_id") id: String = Const.clientId,
#Query("client_secret") secret: String = Const.clientSecret,
#Query("v") date: String
): mainResponse
}
MainActivity.kt
private val viewModel by viewModels<ViewModel>()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
viewModel.getData(
longLat = "40.7,-74",
date = "20210718" // date format is: YYYYMMDD
)
viewModel.data
.observe(this, Observer {
it?.let { res ->
res.response.venues.forEach { venue ->
val name = venue.name
Log.d("name ",name)
}
}
})
}
}

Error Expected BEGIN_OBJECT but was BEGIN_ARRAY at line 1 column 2

I m running to a weird problem with my api response i m using retrofit to execute the request and observing the data using rxjava observabl , i checked my code over and over but nothing is working for me , i don't know what is wrong
This is the json file
[
{
"country":"World",
"cases":13854510,
"todayCases":170435,
"deaths":589937,
"todayDeaths":3727,
"recovered":8241308,
"active":5023265,
"critical":59929,
"casesPerOneMillion":1777,
"deathsPerOneMillion":75,
"totalTests":0,
"testsPerOneMillion":0
}
]
*This is my model class
class CountryModel(
var active: String?,
var cases: String?,
var casesPerOneMillion: String?,
var country: String?,
var critical: String?,
var deaths: String?,
var recovered: String?,
var todayCases: String?,
var todayDeaths: String?
)
class wrapCountryModel(var countries : ArrayList<CountryModel>)
This is my interface for the api call
#GET("countries")
fun getCountriesData() : Flowable<wrapCountryModel>
Retrofit Singleton
fun getCountriesStats() : Observable<wrapCountryModel>{
return Retrofit.Builder().baseUrl(Utilities.COUNTRIESAPILINK)
.addConverterFactory(GsonConverterFactory.create())
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
.build().create(ApiInterface::class.java).getCountriesData()
.subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread()).toObservable()
}
This is the final Step getting data
RetrofitSingleton().getCountriesStats().subscribe(object : Observer<wrapCountryModel>{
override fun onComplete() {
}
override fun onSubscribe(d: Disposable) {
}
override fun onNext(t: wrapCountryModel) {
val country = t.countries[0].country
val cases = t.countries[0].cases
val todaycases = t.countries[0].todayCases
val deaths = t.countries[0].deaths
val todaydeath = t.countries[0].todayDeaths
val recovered = t.countries[0].recovered
val critical = t.countries[0].critical
val active = t.countries[0].active
val casesPerOneMillion = t.countries[0].casesPerOneMillion
val countryModel =
CountryModel(country!!,cases!!,todaycases!!,deaths!!,todaydeath!!,recovered!!,critical!!,active!!,casesPerOneMillion!!)
iView.getCountriesList(arrayListOf(countryModel))
}
Any Help is appreciated , thank you

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/

Sorting data from json in Kotlin

I have data from json file which I display in recyclerview in my app. I'm trying to sort this data by year. That's how my code looks:
In MainActivity.kt everythings happend in fetchJson() function
private fun fetchJson(jsonUrl: String) {
Log.d(TAG, "Attempting to fetch json")
val request = okhttp3.Request.Builder().url(jsonUrl).build()
val client = OkHttpClient()
client.newCall(request).enqueue(object: Callback {
override fun onFailure(call: Call, e: IOException) {
Log.d(TAG, "Failed to execute request")
}
override fun onResponse(call: Call, response: Response) {
val body = response.body()?.string()
Log.d(TAG, "$body")
val gson = GsonBuilder().create()
val homeFeed = gson.fromJson(body, HomeFeed::class.java)
homeFeed.standups.sortedWith(compareBy({it.year}))
runOnUiThread {
rv.adapter = Adapter(homeFeed)
}
}
})
}
fun <T> compareBy(vararg selectors: (T) -> Comparable<*>?): Comparator<T> {
return Comparator<T> { a, b -> compareValuesBy(a, b, *selectors) }
}
class HomeFeed is here:
class HomeFeed(val standups: List<StandUps>)
and data class StandUps:
data class StandUps(
val artist: String,
val title: String,
val year: String,
val poster: String,
val description: String,
val netflix_link: String,
val imdb_rate: String,
val imdb_link: String,
val duration_min: String
)
It doesn't shows any errors or warnings, it just doesn't do anything. How could I achieve this?
You have to first store the sorted list in another variable and then use that variable to pass it to your adapter
val homeFeed = gson.fromJson(body, HomeFeed::class.java)
val sortedHomeFeed = homeFeed.standups.sortedWith(compareBy({it.year}))
runOnUiThread {
rv.adapter = Adapter(sortedHomeFeed)
}
The reason for this is, changes are not made to the original list following the concepts of immutability.
Kotlin gives you easy sorting. Jus like below
make a temp object (i.e)., tempFilterData here
val standUps = tempFilterData?.sortedWith(compareBy({ it.Year }))
Now you can get the sorted data based on YEAR
If you want to sort your list ascending by a year you can do this:
val sortedStandUps = homeFeed.standups.sortedBy { it.year }
If you want to sort list descending do this:
val sortedStandUps = homeFeed.standups.sortedByDescending { it.year }

Categories

Resources