I have a method which should return String value. The return value is correct and shows needed value string but in fragment I received Kotlin.Unit instead of that value.
Here what I see when debug:
here is my function:
fun getName() {
var name: String? = null
uploadPerson({ res ->
name = res.getJSONObject("person").getString("name")
},
{ err ->
Log.d("TAG", "error: $err")
})
val id = "5586"
val url =
"https://$id/api/person/get?&$name"
val resRequest = JsonObjectRequest(
Request.Method.GET, requestUrl, null,
{ response ->
},
{ error ->
}
)
queue.add(resRequest)
}
uploadPerson function:
fun uploadPerson(
result: (res: JSONObject) -> Unit,
error: ((err: String) -> Unit)? = null
) {
val params = HashMap<String, Any>()
params["id"] = person.id
params["name"] = person.name
val jsonObject = JSONObject(params as Map<*, *>)
val id = "5545"
val requestUrl = "https://id/api/person/UploadPerson";
val jsonObjectRequest =
object : JsonObjectRequest(Request.Method.POST, requestUrl, jsonObject,
{ response ->
try {
if (response.getBoolean("success")) {
val resultObj = response.getJSONObject("person")
} else {
}
} catch (e: JSONException) {
e.printStackTrace()
}
uploadResult(response);
},
{ error ->
// TODO: Handle error
uploadError?.invoke(error.toString());
}
) {
override fun getHeaders(): MutableMap<String, String> {
return getAuthHeaders()
}
}
queue.add(jsonObjectRequest);
}
I think that it is because of result uoloadPerson function is Unit and I need return String type in getName function. But I can't understand how to fix this problem
Related
private fun fetchData() {
val url = "http://newsapi.org/v2/top-headlines?country=in&excludeDomains=stackoverflow.com&sortBy=publishedAt&language=en&apiKey=3cb2e739dbcf4bc2951e869186679998"
// val url = "http://my-json-feed"
Log.d(TAG,"url is fine")
val jsonObjectRequest = JsonObjectRequest(
// Log.d(TAG,"inside obj")
Request.Method.GET,url,null,
{ response ->
// textView.text = "Response: %s".format(response.toString()
Log.d(TAG,"inside rsponse")
val it = JSONObject()
val newsJasonArray = it.getJSONArray("articles")
val newsArray = ArrayList<News>()
for (i in 0 until newsJasonArray.length()) {
Log.d(TAG,"inside for loop")
val newsJasonObject = newsJasonArray.getJSONObject(i)
Log.d(TAG,"jason object is done")
val news = News(
newsJasonObject.getString("title"),
newsJasonObject.getString("author"),
newsJasonObject.getString("url"),
newsJasonObject.getString("urlToImage"),
//Log.d(TAG,"objects are fine")
)
newsArray.add(news)
Log.d(TAG,"news is added to array")
}
adapter.updateNews(newsArray)
Log.d(TAG,"adapter is getting")
},
{
error ->
// TODO: Handle error
}
)
I want to get the result from volley and then parse the result to create a model arraylist and the functional that calling the volley request should return the arraylist, the function is called from various other activities and code is written by someone else so I need to figure out a way to achieve the same within this class
Here's the full class code
class Mock #Inject constructor(private val context: Context) {
val url = Constant.prefix + "get_short_videos.php"
fun loadMockData(): ArrayList<StoriesDataModel>? {
// val mockData = context.resources.openRawResource(R.raw.stories_data)
// val dataString = mockData.bufferedReader().readText()
val scope = CoroutineScope(Dispatchers.Main).launch {
val data = getData()
}
Log.e("Rresponse",dataString+"-");
val gson = Gson()
val storiesType = object : TypeToken<ArrayList<StoriesDataModel>>() {}.type
val storiesDataModelList = gson.fromJson<ArrayList<StoriesDataModel>>(dataString, storiesType)
return storiesDataModelList
}
suspend fun getData() = suspendCoroutine<String> { cont ->
val requestQueue = Volley.newRequestQueue(context)
val postRequest: StringRequest = object : StringRequest(
Method.POST, url,
Response.Listener { response: String? ->
Log.e("response",response.toString());
cont.resume(response.toString())
},
Response.ErrorListener { error: VolleyError ->
error.printStackTrace()
Toast.makeText(context, "Check your internet connection", Toast.LENGTH_SHORT)
.show()
}
) {
override fun getParams(): Map<String, String>? {
val params: MutableMap<String, String> = HashMap()
params["mobile"] = context.getSharedPreferences(Constant.prefs, Context.MODE_PRIVATE).getString("mobile","").toString();
params["length"] = "0";
return params
}
}
postRequest.retryPolicy =
DefaultRetryPolicy(
0,
DefaultRetryPolicy.DEFAULT_MAX_RETRIES,
DefaultRetryPolicy.DEFAULT_BACKOFF_MULT
)
requestQueue.add(postRequest)
}
}
I tried various different ways given on internet but not able to figure out a way, new to kotlin so don't have much idea about Coroutine, any help is appreciated, Thanks in advance
These suspendCoroutine and CoroutineScope added by me so these might not be something that should be here
Able to figure out a way to solve the issue, here's answer if anyone needs this
class Mock #Inject constructor(private val context: Context) {
val url = Constant.prefix + "get_short_videos.php"
private var parentJob = Job()
private val coroutineContext: CoroutineContext get() = parentJob + Dispatchers.Main
private val scope = CoroutineScope(coroutineContext)
suspend fun loadMockData(): ArrayList<StoriesDataModel>? {
// val mockData = context.resources.openRawResource(R.raw.stories_data)
// val dataString = mockData.bufferedReader().readText()
var dataString = "";
// val scope = CoroutineScope(Dispatchers.Main).launch {
// dataString = getData()
// }
scope.async(Dispatchers.IO) {
val success = async { getData() }
dataString = success.await()
Log.e("Rresponse2",dataString+"-");
}.await();
Log.e("Rresponse",dataString+"-");
val gson = Gson()
val storiesType = object : TypeToken<ArrayList<StoriesDataModel>>() {}.type
val storiesDataModelList = gson.fromJson<ArrayList<StoriesDataModel>>(dataString, storiesType)
return storiesDataModelList
}
suspend fun getData() = suspendCoroutine<String> { cont ->
val requestQueue = Volley.newRequestQueue(context)
val postRequest: StringRequest = object : StringRequest(
Method.POST, url,
Response.Listener { response: String? ->
Log.e("response",response.toString());
cont.resume(response.toString())
},
Response.ErrorListener { error: VolleyError ->
error.printStackTrace()
Toast.makeText(context, "Check your internet connection", Toast.LENGTH_SHORT)
.show()
}
) {
override fun getParams(): Map<String, String>? {
val params: MutableMap<String, String> = HashMap()
params["mobile"] = context.getSharedPreferences(Constant.prefs, Context.MODE_PRIVATE).getString("mobile","").toString();
params["length"] = "0";
return params
}
}
postRequest.retryPolicy =
DefaultRetryPolicy(
0,
DefaultRetryPolicy.DEFAULT_MAX_RETRIES,
DefaultRetryPolicy.DEFAULT_BACKOFF_MULT
)
requestQueue.add(postRequest)
}
}
Also requires to make function suspended from the main function called
I can not override getParam like shown below. I hope someone can explain how to override getParam in Kotlin.
build.gradle
implementation 'com.android.volley:volley:1.1.1'
fun testpost(button: Button)
{
val url = "http://192.168.178.23/insertcode.php"
val queue = Volley.newRequestQueue(this)
val stringRequest = StringRequest(Request.Method.POST, url,
{ response ->
button.text = "Response is: ${response}"
},
{ button.text = "That didn't work!" })
{
override fun getParam(){
}
}
queue.add(stringRequest)
}
Typing object before StringRequest and now the method getParams is available.
The code looks like this:
fun testpost(button: Button)
{
val url = "http://192.168.178.23/insertcode.php"
val queue = Volley.newRequestQueue(this)
val stringRequest = object :StringRequest(Request.Method.POST, url,
{ response ->
button.text = "Response is: ${response}"
},
{ button.text = "That didn't work!" })
{
//Press Ctr + O to find getParams
override fun getParams(): MutableMap<String, String> {
val hashMap = HashMap<String, String>()
hashMap.put("name", "peter")
return hashMap
}
}
queue.add(stringRequest)
}
You can't, it's not exposed in StringRequest or any other built in request for that matter. If that's indeed what you need to do, you have to unfortunately create your own custom request.
Below is an example for a Custom StringRequest which allows us to specify params in its constructor (Kotlin):
import androidx.annotation.GuardedBy
import com.android.volley.NetworkResponse
import com.android.volley.Request
import com.android.volley.Response
import com.android.volley.toolbox.HttpHeaderParser
import java.io.UnsupportedEncodingException
import java.nio.charset.Charset
class CustomStringRequest(
method: Int,
url: String,
listener: Response.Listener<String>,
errorListener: Response.ErrorListener?,
private val params: MutableMap<String, String>
) : Request<String>(method, url, errorListener) {
private val lock = Any()
#GuardedBy("lock")
private var listener: Response.Listener<String>? = listener
override fun getParams(): MutableMap<String, String> {
return params
}
override fun cancel() {
super.cancel()
synchronized(lock) { listener = null }
}
override fun deliverResponse(response: String) {
var listener: Response.Listener<String>?
synchronized(lock) { listener = this.listener }
if (listener != null) {
listener!!.onResponse(response)
}
}
override fun parseNetworkResponse(response: NetworkResponse): Response<String> {
val parsed: String = try {
String(response.data, Charset.forName(HttpHeaderParser.parseCharset(response?.headers)))
} catch (e: UnsupportedEncodingException) {
// Since minSdkVersion = 8, we can't call
// new String(response.data, Charset.defaultCharset())
// So suppress the warning instead.
String(response.data)
}
return Response.success(
parsed,
HttpHeaderParser.parseCacheHeaders(response)
)
}
}
You would then use it like:
// Instantiate the RequestQueue.
val queue = Volley.newRequestQueue(activity)
val url = "YOUR_URL"
// Request a string response from the provided URL.
val stringRequest = CustomStringRequest(
Request.Method.POST, url,
Response.Listener { response ->
// TODO do something with response
},
Response.ErrorListener {
// TODO handle errors
},
hashMapOf("name" to "value") // TODO add your params here
)
// Add the request to the RequestQueue.
queue.add(stringRequest)
In my viewmodel I have 2 api calls which returns same object. However I created 2 different MutableLiveData objects but I am not able to observe the 2nd object.
This is my code in fragment
private fun initObservables() {
holidayViewModel.progressDialog?.observe(this, Observer {
if (it!!) customeProgressDialog?.show() else customeProgressDialog?.dismiss()
})
holidayViewModel.apiResponse?.observe(
viewLifecycleOwner,
androidx.lifecycle.Observer { response ->
if (response.dataList != null) {
response.dataList!!.removeAt(0)
if (requireArguments().getString("file_type")
.equals(NetworkConstant.FILE_TYPE_LOH, ignoreCase = true)
) {
val data = Data()
data.CountryId = "0"
data.CountryName = "Main organisation"
response.dataList!!.add(0, data)
}
val holidayAdapter = CountryAdapter(response.dataList)
binding.holiday.adapter = holidayAdapter
holidayAdapter.notifyDataSetChanged()
holidayAdapter.setListener(this)
}
})
holidayViewModel.pdfLink?.observe(
viewLifecycleOwner,
androidx.lifecycle.Observer { response ->
utils.openPdf(response.dataList!!.get(0)?.filePath)
})
}
This is the viewmodel class
class HolidayViewModel(networkCall: NetworkCall) : ViewModel() {
var progressDialog: SingleLiveEvent<Boolean>? = null
var apiResponse: MutableLiveData<ApiResponse>? = null
var pdfLink: MutableLiveData<ApiResponse>? = null
var networkCall: NetworkCall;
init {
progressDialog = SingleLiveEvent<Boolean>()
apiResponse = MutableLiveData<ApiResponse>()
this.networkCall = networkCall
}
fun countries(username: String?, userId: String?) {
progressDialog?.value = true
val apiPost = ApiPost()
apiPost.userName = username
apiPost.UserId = userId
networkCall.getCountries(apiPost).enqueue(object : Callback<ApiResponse?> {
override fun onResponse(
call: Call<ApiResponse?>,
response: Response<ApiResponse?>
) {
progressDialog?.value = false
apiResponse?.value = response.body()
}
override fun onFailure(
call: Call<ApiResponse?>,
t: Throwable
) {
progressDialog?.value = false
}
})
}
fun fetchPdf(
username: String?,
password: String?,
userId: String?,
countryId: String?,
fileType: String?
) {
progressDialog?.value = true
val apiPost = ApiPost()
apiPost.userName = username
apiPost.password = password
apiPost.UserId = userId
apiPost.CountryId = countryId
apiPost.FileType = fileType
networkCall.getPDF(apiPost).enqueue(object : Callback<ApiResponse?> {
override fun onResponse(
call: Call<ApiResponse?>,
response: Response<ApiResponse?>
) {
progressDialog?.value = false
pdfLink?.value = response.body()
}
override fun onFailure(
call: Call<ApiResponse?>,
t: Throwable
) {
progressDialog?.value = false
}
})
}
}
I am trying to observe pdfLink object , however the API is called but I never get the callback in my fragment for this object.
What is wrong here?
The problem is pdfLink is always null in viewModel.
You've declared var pdfLink: MutableLiveData<ApiResponse>? = null but haven't initialized yet. And since you are null checking it with ?, it never throws exception.
Try this:
init {
progressDialog = SingleLiveEvent<Boolean>()
apiResponse = MutableLiveData<ApiResponse>()
pdfLink = MutableLiveData<ApiResponse>() // Add this line inside init
this.networkCall = networkCall
}
A silly mistake forget to initialize it
pdfLink = MutableLiveData<ApiResponse>()
I have to call an API asynchronously. To do it i'm using a coroutine, but i have to wait until the API is called to load the data. The problem is the next:
The await is not working as I want, it's not waiting until the API gives all the data.
Is the await what I need? Here is the code:
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_see)
launch { loaddata() }
/* some other code here*/
}
suspend fun loadData(){
val readData = async { read() }
readData.join()
readData.await()
val showScreen = async { refreshList() }
showScreen.join()
showScreen.await()
}
fun read(){
val stringRequest = object : StringRequest(Request.Method.POST, URL, Response.Listener<String>{ s ->
try {
val array = JSONArray(s)
for (i in 0..array.length() - 1) {
val objectAccount = array.getJSONObject(i)
val account = Account(
objectAccount.getString(value),
objectAccount.getString(value),
objectAccount.getString(value))
listAccount.add(account)
}
}catch (e: JSONException){
e.printStackTrace()
}
}, Response.ErrorListener { error: VolleyError? -> Log.e("error", "error") }){
override fun getParams(): Map<String, String> {
val params = HashMap<String, String>()
params.put("password", value)
params.put("idaccount", value)
return params
}
}
val requesQueue = Volley.newRequestQueue(this)
requesQueue.add<String>(stringRequest)
}
Usually you shouldn't call a async function
requesQueue.add<String>(stringRequest)
in the async coroutine builder
async {}
Sulution #1
you can change your read() method to a synchronous request.
Can I do a synchronous request with volley?
and run it with CommonPool
async(CommonPool) {
read()
}
Solution #2
wrap your async http call into a suspend function
I am NOT familiar with Volley, so maybe the code needs tweak
suspend fun read() {
return suspendCancellableCoroutine { continuation ->
val stringRequest = object : StringRequest(Request.Method.POST, URL, Response.Listener<String> { s ->
try {
val array = JSONArray(s)
for (i in 0..array.length() - 1) {
val objectAccount = array.getJSONObject(i)
val account = Account(
objectAccount.getString(value),
objectAccount.getString(value),
objectAccount.getString(value))
listAccount.add(account)
}
} catch (e: JSONException) {
e.printStackTrace()
// notice this
continuation.resumeWithException(e)
}
// notice this
continuation.resume()
}, Response.ErrorListener { error: VolleyError? ->
Log.e("error", "error")
// notice this
if (!continuation.isCancelled)
continuation.resumeWithException()
}) {
override fun getParams(): Map<String, String> {
val params = HashMap<String, String>()
params.put("password", value)
params.put("idaccount", value)
return params
}
}
val requesQueue = Volley.newRequestQueue(this)
requesQueue.add<String>(stringRequest)
continuation.invokeOnCompletion {
if (continuation.isCancelled)
try {
cancel()
} catch (ex: Throwable) {
//Ignore cancel exception
}
}
}
}
and call it like this
suspend fun loadData(){
read()
val showScreen = async { refreshList() }
showScreen.join()
showScreen.await()
}