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/
Related
I'm write code for JSON parsing, but JSON data is not retrieved. What would be the issue?
Below is the Data object class
data class UsersItem(
val avatar_url: String,
val events_url: String,
val followers_url: String,
val following_url: String,
val gists_url: String,
val gravatar_id: String,
val html_url: String,
val id: Int,
val login: String,
val node_id: String,
val organizations_url: String,
val received_events_url: String,
val repos_url: String,
val site_admin: Boolean,
val starred_url: String,
val subscriptions_url: String,
val type: String,
val url: String
)
Below is the List defined
class Users : ArrayList<UsersItem>()
Below is the ApiUtilities
object ApiUtilities {
private val BaseURl = "https://api.github.com/"
fun getInstance(): Retrofit {
return Retrofit.Builder().baseUrl(BaseURl)
.addConverterFactory(GsonConverterFactory.create()).build()
}
}
Below is the ApiInterface
interface ApiInterface {
#GET("/Users")
suspend fun getUsers(): Response<Users>
}
Below is the MainActivity,
I just tested to get the JSON Response with login id, but i'm not getting the result.
Please correct the code or give any suggestions
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val helloTxt = findViewById<TextView>(R.id.txt) as TextView
val userApi = ApiUtilities.getInstance().create(ApiInterface::class.java)
GlobalScope.launch {
val result = userApi.getUsers()
if (result.isSuccessful && result.body() != null) {
result.body()?.forEach {
Toast.makeText(this#MainActivity, "${it.id}", Toast.LENGTH_LONG).show()
Log.d("Manju", "${it.login}")
helloTxt.setText("${it.login}")
}
}
}
}
}
I think that you schould use "/users" with small "u":
interface ApiInterface {
#GET("/users")
suspend fun getUsers(): Response<Users>
}
insted of:
interface ApiInterface {
#GET("/Users")
suspend fun getUsers(): Response<Users>
}
I'm currently making a sample project about diagrams. I'm starting to use MVVM architecture recently, and I got stuck when the response is null. I also checked the Mutable Live Data to make sure that it is calling the API. Here's some of my code and the error-tag:
Model.kt
data class Model(
#SerializedName("FID") val FID: Int,
#SerializedName("region") val region: String,
#SerializedName("positive") val positive: Float
) {
}
ModelWrap.kt
data class ModelWrap(#SerializedName("samplesAPI") val attributes: Model){
}
ApiClient.kt
object ApiClient {
var retrofitService: ApiInterface? = null
const val BASE_URL = "https://sampleapi.../"
fun getApiSample() : ApiInterface {
if (retrofitService == null){
val retrofit = Retrofit.Builder()
.baseUrl(BASE_URL)
.addConverterFactory(GsonConverterFactory.create())
.build()
retrofitService = retrofit.create(ApiInterface::class.java)
}
return retrofitService!!
}
}
ApiInterface.kt
interface ApiInterface {
#GET("samples")
fun getSampleData(): Call<List<ModelWrap>>
}
MainViewModel.kt
class MainViewModelconstructor(private val repository: ModelRepository) : ViewModel(){
val sampleList= MutableLiveData<List<ModelWrap>>()
val errorMessage = MutableLiveData<String>()
fun getSampleData(pieChart: PieChart){
val response = repository.getSampleData()
response.enqueue(object : Callback<List<ModelWrap>> {
override fun onResponse(
call: Call<List<ModelWrap>>,
response: Response<List<ModelWrap>>
) {
sampleList.postValue(response.body())
}
override fun onFailure(call: Call<List<ModelWrap>>, t: Throwable) {
errorMessage.postValue(t.message)
}
})
}
}
MainViewModelFactory.kt
class MainViewModelFactoryconstructor(private val repository: MainRepository) : ViewModelProvider.Factory {
override fun <T : ViewModel?> create(modelClass: Class<T>): T {
return if (modelClass.isAssignableFrom(MainViewModel::class.java)){
MainViewModel(this.repository) as T
} else {
throw IllegalArgumentException("Sample ViewModel Not Found")
}
}
}
MainRepository.kt
class MainRepository constructor(private val retrofitService: ApiInterface){
fun getSampleData() = retrofitService.getSampleData()
}
MainActivity.kt
class MainActivity : AppCompatActivity() {
private lateinit var pieChart: PieChart
lateinit var sampleViewModel: MainViewModel
private val sampleService = ApiClient.getApiSample()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
pieChart = findViewById(R.id.PieChart)
sampleViewModel= ViewModelProvider(this, MainViewModelFactory(MainRepository(sampleService))).get(MainViewModel::class.java)
getPieChart(pieChart)
}
private fun getPieChart(pieCharts: PieChart) {
mainViewModel.mainList.observe(this, Observer {
Log.d("TAG sample" , "onCreate PieChart: $it")
Log.d("Tag Samples Response" , response.body().toString())
if (it != null) {
val sampleEntries: List<PieEntry> = ArrayList()
for ((attributes) in it!!) {
sampleEntries.toMutableList()
.add(PieEntry(attributes.positive, attributes.region))
//........................................................................
val description = Description()
description.text = "Samples Data"
pieChart.description = description
pieChart.invalidate()
}
}
})
mainViewModel.errorMessage.observe(this, Observer { })
mainViewModel.getSampleData(pieCharts)
}
}
and Lastly, here's some or log message:
V/InputMethodManager: Starting input: tba=android.view.inputmethod.EditorInfo#8b795c0 nm : com.example.diargram ic=null
D/Tag Sample Response: null
D/TAG Sample: onCreate PieChart: null
E/libc: Access denied finding property "ro.serialno"
V/StudioTransport: Agent command stream started.
V/StudioTransport: Transport agent connected to daemon.
I would appreciate it if someone can help me :D, Thank you
Finally, I found a solution for my problem:
I type the wrong endpoint inside the interface class and it should be like this:
interface ApiInterface {
#GET("sample")
fun getSampleData(): Call<List> }
When it comes to assigning the livedata to the view, based on my JSON I should call ArrayList instead of List
List item
Before :
val sampleEntries: List = ArrayList()
After :
val sampleEntries: ArrayList<PieEntry> = ArrayList()
I have a sealed class for state handling of my Retrofit responses. It's members take a generic type. I would like to get Retrofit to be able to return the proper object, but I am stuck at this error: Unable to create converter for com.my.app.DataResult<?> - Cannot serialize abstract class com.my.app.DataResult
This is my DataResult class:
sealed class DataResult<out T> {
data class Success<out T>(val data: T?) : DataResult<T>()
data class Error<out T>(val code: Int? = null, val error: Exception? = null) : DataResult<T>()
object NetworkError : DataResult<Nothing>()
fun isSuccess() = this is Success<*>
fun isError() = this is Error<*>
fun data() = if (isSuccess()) (this as Success<T>).data else null
}
fun successResult() = DataResult.Success(null)
fun <T> successResult(data: T?) = DataResult.Success(data)
fun errorResult() = DataResult.Error<Nothing>(null)
This is the rest of my current implementation:
class NetworkClient(private val httpClient: HttpClient) {
private val baseUrl: String = "some url"
private val retrofit = Retrofit.Builder()
.baseUrl(mockend)
.addCallAdapterFactory(MyCallAdapterFactory())
.addConverterFactory(MoshiConverterFactory.create())
.client(httpClient.get())
.build()
private val apiService: ApiService = retrofit.create(StaApiService::class.java)
suspend fun <T> sendGet(endPoint: EndPoint, input: String): DataResult<T> {
val result = apiService.sendGetRequest<T>(endPoint.stringValue, queryMapOf(Pair("query", input)))
when (result) {
// do stuff here?
}
return result
}
}
interface ApiService {
#GET
suspend fun <T> sendGetRequest(
#Url url: String,
#QueryMap parameters: Map<String, String>): DataResult<T>
#GET
suspend fun <T> sendGetListRequest(
#Url url: String,
#QueryMap parameters: Map<String, String>): DataResult<List<T>>
}
abstract class CallDelegate<TIn, TOut>(
protected val proxy: Call<TIn>
) : Call<TOut> {
override fun execute(): Response<TOut> = throw NotImplementedError()
final override fun enqueue(callback: Callback<TOut>) = enqueueImpl(callback)
final override fun clone(): Call<TOut> = cloneImpl()
override fun cancel() = proxy.cancel()
override fun request(): Request = proxy.request()
override fun isExecuted() = proxy.isExecuted
override fun isCanceled() = proxy.isCanceled
abstract fun enqueueImpl(callback: Callback<TOut>)
abstract fun cloneImpl(): Call<TOut>
}
class ResultCall<T>(proxy: Call<T>) : CallDelegate<T, DataResult<T>>(proxy) {
override fun enqueueImpl(callback: Callback<DataResult<T>>) = proxy.enqueue(object : Callback<T> {
override fun onResponse(call: Call<T>, response: Response<T>) {
val code = response.code()
val result: DataResult<T> = if (code in 200 until 300) {
val body = response.body()
DataResult.Success(body)
} else {
DataResult.Error(code)
}
callback.onResponse(this#ResultCall, Response.success(result))
}
override fun onFailure(call: Call<T>, t: Throwable) {
val result: DataResult<Nothing> = if (t is IOException) {
DataResult.NetworkError
} else {
DataResult.Error(null)
}
callback.onResponse(this#ResultCall, Response.success(result))
}
})
override fun cloneImpl() = ResultCall(proxy.clone())
}
class ResultAdapter(
private val type: Type
) : CallAdapter<Type, Call<DataResult<Type>>> {
override fun responseType() = type
override fun adapt(call: Call<Type>): Call<DataResult<Type>> = ResultCall(call)
}
class MyCallAdapterFactory : CallAdapter.Factory() {
override fun get(
returnType: Type,
annotations: Array<Annotation>,
retrofit: Retrofit
) = when (getRawType(returnType)) {
Call::class.java -> {
val callType = getParameterUpperBound(0, returnType as ParameterizedType)
when (getRawType(callType)) {
Result::class.java -> {
val resultType = getParameterUpperBound(0, callType as ParameterizedType)
ResultAdapter(resultType)
}
else -> null
}
}
else -> null
}
}
The above code is largely inspired by this answer to another question,
but I'm trying to add Generics to the mix, so I don't have to put every request into the interface by hand. Is it possible or not? I have tried for hours, also tried to build an adapter for the sealed class but failed. Has someone a good resource how this can be done?
As you can also see in the code I'd like to also be able to receive lists. Any tips here are much appreciated too.
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
I was inspired by the writing of this adapter to Valery Katkov's answer answer
My Retrofit call adapter is able to transform the JSON of normal objects correctly, but when I expect from a call a List<Object>, Retrofit returns me a List<LinkedTreeMap>. It cannot parse Object within the list
Exception
java.lang.ClassCastException: com.google.gson.internal.LinkedTreeMap cannot be cast to com.example.networkcalladapter.Post
CallAdapter Factory And CallAdapter
class NetworkCallAdapterFactory : CallAdapter.Factory() {
override fun get(
returnType: Type,
annotations: Array<Annotation>,
retrofit: Retrofit
) = when (getRawType(returnType)) {
Call::class.java -> {
val callType = getParameterUpperBound(0, returnType as ParameterizedType)
when (getRawType(callType)) {
ResponseNetwork::class.java -> {
require(callType is ParameterizedType){ "resource must be paramterized" }
val resultType = getParameterUpperBound(0, callType)
ResponseNetworkAdapter<Any>(getRawType(resultType))
}
else -> null
}
}
else -> null
}
}
class ResponseNetworkAdapter<T: Any>(
private val type: Type
) : CallAdapter<T, Call<ResponseNetwork<T>>> {
override fun responseType() = type
override fun adapt(call: Call<T>): Call<ResponseNetwork<T>> = ResponseNetworkCall(call)
}
abstract class CallDelegate<TIn, TOut>(
protected val proxy: Call<TIn>
) : Call<TOut> {
override fun execute(): Response<TOut> = throw NotImplementedError()
final override fun enqueue(callback: Callback<TOut>) = enqueueImpl(callback)
final override fun clone(): Call<TOut> = cloneImpl()
override fun cancel() = proxy.cancel()
override fun request(): Request = proxy.request()
override fun isExecuted() = proxy.isExecuted
override fun isCanceled() = proxy.isCanceled
abstract fun enqueueImpl(callback: Callback<TOut>)
abstract fun cloneImpl(): Call<TOut>
}
class ResponseNetworkCall<T: Any>(proxy: Call<T>) : CallDelegate<T, ResponseNetwork<T>>(proxy) {
override fun enqueueImpl(callback: Callback<ResponseNetwork<T>>) {
proxy.enqueue(object : Callback<T> {
override fun onResponse(call: Call<T>, response: Response<T>) {
callback.onResponse(this#ResponseNetworkCall, Response.success(ResponseNetwork.create(response)))
}
override fun onFailure(call: Call<T>, t: Throwable) {
callback.onResponse(this#ResponseNetworkCall, Response.success(ResponseNetwork.create(Exception(t))))
}
})
}
override fun cloneImpl() = ResponseNetworkCall(proxy.clone())
}
ResponseNetwork
sealed class ResponseNetwork<T> {
companion object {
fun <T> create(error: Exception): ResponseNetworkError<T> {
return ResponseNetworkError(error)
}
fun <T> create(response: Response<T>): ResponseNetwork<T> {
return if (response.isSuccessful) {
response.body()?.let {
ResponseNetworkSuccess(response.code(), response.headers(), it)
} ?: ResponseNetworkEmpty(
response.code(),
response.errorBody()?.string() ?: "unknown error"
)
} else {
val msg = response.errorBody()?.string()
ResponseNetworkError(Exception(msg))
}
}
}
}
data class ResponseNetworkSuccess<T>(
val code: Int,
val header: Headers,
val body: T
) : ResponseNetwork<T>()
data class ResponseNetworkEmpty<T>(
val code: Int,
val message: String
) : ResponseNetwork<T>()
data class ResponseNetworkError<T>(
val exception: Exception
) : ResponseNetwork<T>()
Remote Api
#GET("posts")
suspend fun getPost(): ResponseNetwork<List<Post>>
Retrofit
Retrofit.Builder()
.baseUrl("https://jsonplaceholder.typicode.com/")
.addConverterFactory(GsonConverterFactory.create())
.addCallAdapterFactory(NetworkCallAdapterFactory())
.build()
.create(RemoteApi::class.java)
Post Model
data class Post(val userId: Int,
val id: Int,
val title: String,
val body: String)
Someone understands why retrofit always comes back to me List<LinkedTreeMap> whenever I need a list from the network ?
can you replace your remote API with this and check it.
#GET("posts")
suspend fun getPost(): Deferred<Response<ResponseNetwork<List<Post>>>
i fixed my bug in NetworkCallAdapterFactory
ResponseNetworkAdapter<Any>((resultType))