I am trying to get data from an Http request to my own API. Running the request in my browser (swapping the IP with localhost) gets me:
["Herbalism","Mining","Skinning","Alchemy","Blacksmithing","Enchanting","Engineering","Inscription","Jewelcrafting","Leatherworking","Tailoring","Archaeology","Cooking","Fishing"]
I am using the following code, modified from the example provided here: https://www.tutorialspoint.com/how-to-use-volley-library-to-parse-json-in-android-kotlin-app
It does not print my "Print anything at all". From what I can tell this does nothing. I have tried many different things including suggestions from here: Can I do a synchronous request with volley? and here: Volley Timeout Error and feel that I am in no way closer to getting this request to work. The firewall permits this traffic. The catch JSONException and Response.ErrorListener are also not putting anything out. I am using plain http, no certs or anything. Ultimately I want to populate a Spinner with this data but for now I can't even get this most basic implementation to work.
package com.wowahapp
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.widget.*
import androidx.recyclerview.widget.RecyclerView
import com.android.volley.Request
import com.android.volley.RequestQueue
import com.android.volley.Response
import com.android.volley.toolbox.*
import org.json.JSONException
class AddRecipeActivity : AppCompatActivity() {
lateinit var searchTextView : TextView
private var requestQueue : RequestQueue? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_add_recipe)
searchTextView = findViewById<TextView>(R.id.searchTextView) as TextView
getAllProfessions(searchTextView)
}
fun getAllProfessions(searchText : TextView) {
val url = "http://192.168.0.24:49155/allprofessions"
val request = JsonObjectRequest(Request.Method.GET, url, null, Response.Listener {
response ->try {
val jsonArray = response.getJSONArray("name")
println("Print anything at all!")
for (i in 0 until jsonArray.length()) {
println(jsonArray.getJSONObject(i))
}
} catch (e: JSONException) {
e.printStackTrace()
}
}, Response.ErrorListener { error -> error.printStackTrace() })
requestQueue?.add(request)
}
}
First create custom VolleyWebService class as follows:
class VolleyWebService constructor(context: Context) {
private var INSTANCE: VolleyWebService? = null
companion object {
#Volatile
private var INSTANCE: VolleyWebService? = null
fun getInstance(context: Context) =
INSTANCE ?: synchronized(this) {
INSTANCE ?: VolleyWebService(context).also {
INSTANCE = it
}
}
}
val requestQueue: RequestQueue by lazy {
Volley.newRequestQueue(context.applicationContext)
}
fun <T> addToRequestQueue(req: Request<T>) {
requestQueue.add(req)
}
}
Then modify your function getAllProfessions like this:
fun getAllProfessions(searchText : TextView) {
val url = "http://192.168.0.24:49155/allprofessions"
val request = JsonObjectRequest(Request.Method.GET, url, null, Response.Listener {
response ->try {
val jsonArray = response.getJSONArray("name")
println("Print anything at all!")
for (i in 0 until jsonArray.length()) {
println(jsonArray.getJSONObject(i))
}
} catch (e: JSONException) {
e.printStackTrace()
}
}, Response.ErrorListener { error -> error.printStackTrace() })
//changes made here
VolleyWebService.getInstance(context).addToRequestQueue(request)
}
}
This document explain the implementation of a VolleyWebService class:
http://code.sunnyjohn.in/index.php/2020/12/24/retrieve-data-volley/
You have to instantiate the class, create a new request queue and then add to the request queue.
Related
so I decided to use this documentation about he in-memory cache for the repository pattern here, without using the Coroutines in the first place. For what I have done I created a response from the server to make a query, however I want this data to be in cache. I've done as the documentation is explained, but however once I put the Boolean parameter, it just won't return anything, so I thought it's going to be the return type, but it says that it found a unit within the if-else statement. My objective is I want to return the cache if there is any data in it, if it doesn't have any data, to call from the server the latest data and write it in the cache and then return it to the application. Any ideas or hints would do nicely, if there is something that it didn't made sense of my question, feel free to correct me about it. Thank you in advance.
The DTO Model:
package com.example.spaceflightnews.network.model
data class ArticleResource(
val title: String,
val imageUrl: String,
val summary: String,
val publishedAt: String,
)
DataSource:
package com.example.spaceflightnews.network.datasource
import android.util.Log
import com.example.spaceflightnews.network.Retrofit
import com.example.spaceflightnews.network.model.ArticleResource
import com.example.spaceflightnews.network.spaceflightNewsAPI.ArticlesAPI
import retrofit2.Call
import retrofit2.Callback
import retrofit2.Response
class ArticleNetworkDataSource(
private val serviceApi: ArticlesAPI = Retrofit().getArticleInstance().create(ArticlesAPI::class.java)
) {
//Articles model call and callback
fun getArticleRequest(
callback: DataReadyCallback
) {
val call: Call<List<ArticleResource>> = serviceApi.getArticles()
call.enqueue(object : Callback<List<ArticleResource>> {
override fun onResponse(
call: Call<List<ArticleResource>>,
response: Response<List<ArticleResource>>
) {
response.body()?.run {
callback.onDataReady(this)
}
}
override fun onFailure(call: Call<List<ArticleResource>>, t: Throwable) {
Log.e("Error", t.localizedMessage!!)
}
})
}
interface DataReadyCallback {
fun onDataReady(data: List<ArticleResource>)
}
}
Repository:
class ArticlesRepository(
private val articlesNetworkDataSource: ArticleNetworkDataSource = ArticleNetworkDataSource()
) {
private var latestArticles: List<ArticleResource>? = null
fun getDataListObject(
mutableLiveData: MutableLiveData<List<ArticleResource>>,
isDataAddedInCache: Boolean = false
) {
if (isDataAddedInCache) {
//Calling the query
articlesNetworkDataSource.getArticleRequest(object : ArticleNetworkDataSource.DataReadyCallback {
override fun onDataReady(data: List<ArticleResource>) {
latestArticles = data
latestArticles?.run {
//Send it to the ViewModel
mutableLiveData.value = this
}
}
})
} else {
//If there is data in it, return that cache so it won't call the query again from the server.
latestArticles?.run {
mutableLiveData.value = this
}
}
}
}
I am developing android app and I have implemented success and failure cases in viemodel class but I am getting following mismatch Type mismatch.
Required:
Result!
Found:
Result<Response>
below my NewsViewModel where I have implemented success and failure cases when I am getting data
class NewsViewModel(private val repository: NewsRepository) : ViewModel() {
private var _newsResponse= MutableLiveData<Result<NewsResponse>>()
// Expose to the outside world
val news: LiveData<Result<NewsResponse>> = _newsResponse
#UiThread
fun getNews() {
viewModelScope.launch(Dispatchers.IO) {
try {
val response = repository.getNews()
_newsResponse.postValue(Result.success(response))
} catch (ioe: IOException) {
_newsResponse.postValue(Result.Failure("[IO] error please retry", ioe))
} catch (he: HttpException) {
_newsResponse.postValue(Result.Failure("[HTTP] error please retry", he))
}
}
}
}
below my NewsRepository.ktclass
NewsRepository(
private val apiInterface:NewsInterface
){
suspend fun getNews() = apiInterface.getNews()
}
below my Result class
sealed class Result<out T> {
data class Success<out R>(val value: R): Result<R>()
data class Failure(
val message: String?,
val throwable: Throwable?
): Result<Nothing>()
}
I want to know where I exactly I am making mistake what I have to do in order to fix that problem
below my news Interface
import com.example.newsworldwide.model.NewsResponse
import retrofit2.Response
import retrofit2.http.GET
interface NewsInterface {
#GET("ApiKey")
suspend fun getNews(): Response<NewsResponse>
}
Your NewsInterface is returning Response<NewsResponse> & in your NewsViewModel you're passing it directly to response so it becomes Result.Success<Response<NewsResponse>> at the time of posting. That's why this error.
Solution:
Get value from body() of retrofit response class.
Make it Non-nullable using(!!) as your _newsResponse live-data is accepting NewsResponse which is non-nullable. You might want to handle null case here.
So your final code would look something like this.
class NewsViewModel(private val repository: NewsRepository) : ViewModel() {
private var _newsResponse= MutableLiveData<Result<NewsResponse>>()
// Expose to the outside world
val news: LiveData<Result<NewsResponse>> = _newsResponse
#UiThread
fun getNews() {
viewModelScope.launch(Dispatchers.IO) {
try {
val response = repository.getNews().body()!! //change this line
_newsResponse.postValue(Result.Success(response))
} catch (ioe: IOException) {
_newsResponse.postValue(Result.Failure("[IO] error please retry", ioe))
} catch (he: HttpException) {
_newsResponse.postValue(Result.Failure("[HTTP] error please retry", he))
}
}
}
}
I need to send GET request with body from android application, but it seems that volley ignore it (body). It's always empty on the server side.
I was trying to send body in request and ovverride getBody() - no effect.
In Request.java I saw the comment that getBody only send body for POST or PUT.
Maybe I need to override another method or need to use another library for such task?
My last custom request:
package by.lsd.rmapiconnector
import com.android.volley.NetworkResponse
import com.android.volley.ParseError
import com.android.volley.Response
import com.android.volley.toolbox.HttpHeaderParser
import com.android.volley.toolbox.JsonRequest
import org.json.JSONArray
import org.json.JSONException
import java.io.ByteArrayOutputStream
import java.io.ObjectOutputStream
import java.io.UnsupportedEncodingException
import java.nio.charset.Charset
class CustomJsonRequest(
method: Int,
url: String?,
requestObject: HashMap<String, Any>,
private val mResponseListener: Response.Listener<JSONArray>,
errorListener: Response.ErrorListener?
) : JsonRequest<JSONArray>(
method,
url,
requestObject.toString(),
mResponseListener,
errorListener
) {
private val mRequestObject = requestObject
override fun deliverResponse(response: JSONArray) {
mResponseListener.onResponse(response)
}
override fun parseNetworkResponse(response: NetworkResponse): Response<JSONArray> {
return try {
val json = String(response.data, Charset.forName(HttpHeaderParser.parseCharset(response.headers)))
try {
Response.success(
JSONArray(json),
HttpHeaderParser.parseCacheHeaders(response)
)
} catch (e: JSONException) {
Response.error(ParseError(e))
}
} catch (e: UnsupportedEncodingException) {
Response.error(ParseError(e))
}
}
override fun getHeaders(): Map<String, String> {
val headers: HashMap<String, String> = HashMap()
headers["Accept"] = "application/json"
headers["Content-Type"] = "application/json"
//headers["Transfer-Encoding"] = "chunked"
return headers
}
override fun getBody(): ByteArray {
val byteOut = ByteArrayOutputStream()
val out = ObjectOutputStream(byteOut)
out.writeObject(mRequestObject)
return byteOut.toByteArray()
}
}
I'm learning to use the Retrofit library for different tasks, but don't fully understand how it works yet.
The main task is to get the body if the response code is 200, overwise (all other codes) just set flag:
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import okhttp3.ResponseBody
import retrofit2.Response
import retrofit2.Retrofit
import retrofit2.http.GET
interface APIService {
#GET("/")
suspend fun getRoot(): Response<ResponseBody>
}
...
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
...
button.setOnClickListener {
val url = editText.text.toString()
// url = "https://"+ "google.coN"
val retrofit = Retrofit.Builder()
.baseUrl(url)
.build()
val service = retrofit.create(APIService::class.java)
...
CoroutineScope(Dispatchers.IO).launch {
val response = service.getRoot()
withContext(Dispatchers.Main) {
if (response.isSuccessful){
Ok = true // address is ok
} else {
Ok = false // this address dosnt exist
}
....
}
}
}
}
}
Code works well (remastered from some tutor example) with good links but the app crashes whenever the address is wrong or poorly formatted, it requires a well-formatted URL ("https://"+)
How to modify code and add an exception and do pre-format of URL?
PS: Prob it is better to use OkHTTP directly, but I use integration
of GSON lib with this retrofit code, dropped for clarity
Thanx.
First, Create a sealed class to hold the result
sealed class ApiResult<out T : Any?>
data class Success<out T : Any?>(val data: T) : ApiResult<T>()
data class ApiError(val exception: Exception) : ApiResult<Nothing>()
Now write a helper function to map okhttp response to ApiResult
suspend fun <T : Any> handleApi(
call: suspend () -> Response<T>,
errorMessage: String = "Some errors occurred, Please try again later"
): ApiResult<T> {
try {
val response = call()
if (response.isSuccessful) {
isConnectedToNetwork = true
response.body()?.let {
return Success(it)
}
}
response.errorBody()?.let {
try {
val errorString = it.string()
val errorObject = JSONObject(errorString)
return ApiError(
RuntimeException(if(errorObject.has("message")) errorObject.getString("message") else "Error occurred, Try again Later"))
} catch (ignored: JsonSyntaxException) {
return ApiError(RuntimeException(errorMessage))
}
}
return ApiError(RuntimeException(errorMessage))
} catch (e: Exception) {
if (e is IOException) {
isConnectedToNetwork = false
}
return ApiError(RuntimeException(errorMessage))
}
}
Finally, use below to code to access the result
CoroutineScope(Dispatchers.IO).launch {
val result: ApiResult<ResponseBody> = handleApi( { service.getRoot() } )
when(result){
is ApiResult.Success -> // result.data will give you ResponseBody
is ApiResult.ApiError -> // result.exception will provide the error
}
}
There are few things which can help you with this, it will be more efficient:
Create a view model and create an instance of that in your activity.
In the view model, create a method for executing background tasks, like this:
private fun loadNetworkRequest(block: suspend () -> Unit): Job {
return viewModelScope.launch {
try {
block()
}catch (ex: Exception) {
Toast.makeText(appContext, ex.message, Toast.LENGTH_SHORT).show()
}
}
}
Add the suspend keyword for the request in the service file, which you want to execute using this method.
#GET("category/")
suspend fun getCategories(): Response<CategoryResponseModel>
Execute the request in the view model, like this:
fun performRequest(callback: (Boolean) -> Unit) {
loadNetworkRequest {
val response = service.getRoot()
callback.invoke(response.isSuccessful)
}
}
Call the request method in the activity.
button.setOnClickListener {
....
viewModel.performRequest { response ->
// ok = response
}
}
I'm trying to create a general Request class with kotlin, which I can use to make Request with Volley.
The problem I'm having is that I can not return the response of the Request.
I'm trying to get the response of the request so that I can proces the data.
I can't seem to find a good source which describes how to make a Helper class for making Request
Request class
import android.content.Context
import android.util.Log
import com.android.volley.RequestQueue
import com.android.volley.Response
import com.android.volley.toolbox.StringRequest
import com.android.volley.toolbox.Volley
class Request(var context: Context, var url: String) {
var response : String? = null
fun makePOSTRequest() {
val requestQueue: RequestQueue? = Volley.newRequestQueue(context)
val stringRequest = object : StringRequest(
Method.POST, url,
Response.Listener { response ->
}, Response.ErrorListener { error ->
Log.i("Error", "[" + error + "]")
}) {
override fun getParams(): Map<String, String> {
val params = HashMap<String, String>()
return params
}
}
requestQueue?.add(stringRequest)
}
fun makeGETRequest() {
val requestQueue: RequestQueue? = Volley.newRequestQueue(context)
val stringRequest = object : StringRequest(
Method.GET, url,
Response.Listener { response ->
println(response) // Response: {"message":"ok","locaties"[{"id":"739","name":"Company","code":"","klant":"Client"}]}
this.response = response // Here I'm trying to fill the response var
}, Response.ErrorListener { error ->
Log.i("Error", "[" + error + "]")
}) {
}
requestQueue?.add(stringRequest)
}
}
Implementation
var request = context?.let { Request(it, BuildConfig.API_URL + "getLocatiesLijst.php?name=" + bdl?.getString("name")) }
request?.makeGETRequest()
var response = request?.response
println(response) // This give Null back
I guess the problem is that request is async, but when you run this come, println("") will be called immediately. You should wait for a result.
You can add some callback to the listener or try to use coroutines.
I have been busy with implementing a request helper class with coroutines. And I think I have found a solution to my own problem. I'm using coroutines to make the request async.
Source of solution
Request.kt
import android.content.Context
import com.android.volley.Request
import com.android.volley.Response
import com.android.volley.toolbox.StringRequest
import com.android.volley.toolbox.Volley
import kotlin.coroutines.*;
class Request(var context: Context, var url: String) {
suspend fun makeGetRequest() = suspendCoroutine<String> { cont ->
val queue = Volley.newRequestQueue(context)
val stringRequest = StringRequest(
Request.Method.GET, url,
Response.Listener<String> { response ->
cont.resume("Response is: ${response}")
},
Response.ErrorListener { cont.resume("Something went wrong!") })
queue.add(stringRequest)
}
}
Implementation
class LocationFragment : Fragment(), CoroutineScope {
protected lateinit var job: Job
override val coroutineContext: CoroutineContext
get() = job + Dispatchers.Main
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
var rootView = inflater.inflate(R.layout.location_list, container, false)
//TODO doe verzoek voor lijst van locaties
val bdl = arguments
var request = context?.let { Request(it, BuildConfig.API_URL + "getLocatiesLijst.php?name=" + bdl?.getString("name")) }
job = Job()
launch {
val data = request?.makeGetRequest()
println(data)
}
}
Result
I/System.out: Response is:{"message":"ok","locaties":[{"id":"739","name":"Company","lce_code":"code","klant":"Client"}]}