Not able to fetch data using volley's library - android

I want to fetch the news from the api but when I am running the app, it is showing the toast message which is inside the ErrorListener block. I don't know what is happening please someone help
val APIurl = "https://newsapi.org/v2/top-headlines?country=in&apiKey=f2c9d983c1c44dd3a89ab47aacd520cb"
// Request a string response from the provided URL.
val jsonObjectRequest = JsonObjectRequest(
Request.Method.GET, APIurl, null,
{
val jsonObjectArray = it.getJSONArray("articles")
val newsList = ArrayList<News>()
for(i in 0 until jsonObjectArray.length()){
val currJsonObject = jsonObjectArray.getJSONObject(i)
val news = News(
currJsonObject.getString("title"),
currJsonObject.getString("author"),
currJsonObject.getString("url"),
currJsonObject.getString("urlToImage")
)
newsList.add(news)
}
mAdapter.updateList(newsList)
},
//While opening the app it is showing this message
{
Toast.makeText(this, "Something went wrong!", Toast.LENGTH_SHORT).show()
})
// Add the request to the RequestQueue.
MySingleton.getInstance(this).addToRequestQueue(jsonObjectRequest)
This is my singleton class
class MySingleton(context: Context) {
companion object {
#Volatile
private var INSTANCE: MySingleton? = null
fun getInstance(context: Context) =
INSTANCE ?: synchronized(this) {
INSTANCE ?: MySingleton(context).also {
INSTANCE = it
}
}
}
val requestQueue: RequestQueue by lazy {
// applicationContext is key, it keeps you from leaking the
// Activity or BroadcastReceiver if someone passes one in.
Volley.newRequestQueue(context.applicationContext)
}
fun <T> addToRequestQueue(req: Request<T>) {
requestQueue.add(req)
}
}

Related

Async requests in Kotlin Android

I often get an error android.os.NetworkOnMainThreadException, when I try get info from some api. I know that this problem is related to the main android thread, but I don't understand how to solve it - coroutines, async okhttp, or both?
P.S I have a bad eng, sorry.
My code:
MainAtivity.kt
class MainActivity: AppCompatActivity(), Alert {
private lateinit var binding: ActivityMainBinding
lateinit var api: ApiWeather
var okHttpClient: OkHttpClient = OkHttpClient()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
api = ApiWeather(okHttpClient)
binding.buttonGetWeather.setOnClickListener {
val cityInput = binding.textInputCity.text.toString()
if (cityInput.isEmpty()) {
errorAlert(this, "...").show()
} else {
val city = "${cityInput.lowercase()}"
val limit = "1"
val appId = "key"
val urlGeocoding = "http://api.openweathermap.org/geo/1.0/direct?" +
"q=$city&limit=$limit&appid=$appId"
var status = false
val coordinates: MutableMap<String, Double> = mutableMapOf()
val job1: Job = lifecycleScope.launch {
val geo = api.getGeo(urlGeocoding)
if (geo != null) {
coordinates["lat"] = geo.lat
coordinates["lon"] = geo.lon
status = true
} else {
status = false
}
}
val job2: Job = lifecycleScope.launch {
job1.join()
when(status) {
false -> {
binding.textviewTempValue.text = ""
errorAlert(this#MainActivity, "...").show()
}
true -> {
val urlWeather = "https://api.openweathermap.org/data/2.5/weather?" +
"lat=${coordinates["lat"]}&lon=${coordinates["lon"]}&units=metric&appid=${appId}"
val weather = api.getTemp(urlWeather)
binding.textviewTempValue.text = weather.main.temp.toString()
}
}
}
}
}
}
}
Api.kt
class ApiWeather(cl: OkHttpClient) {
private val client: OkHttpClient
init {
client = cl
}
suspend fun getGeo(url: String): GeocodingModel? {
val request: Request = Request.Builder()
.url(url)
.build()
val responseStr = client.newCall(request).await().body?.string().toString()
val json = Json {
ignoreUnknownKeys = true
}
return try {
json.decodeFromString<List<GeocodingModel>>(responseStr)[0]
} catch (e: Exception) {
return null
}
}
suspend fun getTemp(url: String): DetailWeatherModel {
val request: Request = Request.Builder()
.url(url)
.build()
val responseStr = client.newCall(request).await().body?.string().toString()
val json = Json {
ignoreUnknownKeys = true
}
return json.decodeFromString<DetailWeatherModel>(responseStr)
}
}
The problem is that api.getGeo(urlGeocoding) runs in the current thread. lifecycleScope.launch {} by default has Dispatchers.Main context, so calling api function will run on the Main Thread. To make it run in background thread you need to switch context by using withContext(Dispatchers.IO). It will look like the following:
lifecycleScope.launch {
val geo = withContext(Dispatchers.IO) { api.getGeo(urlGeocoding) }
if (geo != null) {
coordinates["lat"] = geo.lat
coordinates["lon"] = geo.lon
status = true
} else {
status = false
}
when(status) { ... }
}
You are already using coroutines. The problem is that lifecycleScope is tied to main thread. You want to replace it with GlobalScope or coroutineScope (latter is better in terms of complex project, but I assume you are writing pet-project now, so GlobalScope.launch will be fine)
you should replace
lifecycleScope.launch{
with
lifecycleScope.launch(Dispatchers.IO){

How to load JSON data with square bracket

I'm calling data from Breaking bad API https://www.breakingbadapi.com/api/character/random
I'm unable to get data. I think it's because the main Response file has square brackets that I need to call first. But I don't know how to call it. Can I get some help?
Here's my API interface
interface APIRequest {
#GET("character/random")
suspend fun getInfo() : Response<List<ResponseBB>>
}
ResponseBB Class
data class ResponseBB(
#field:SerializedName("ResponseBB")
val responseBB: List<ResponseBBItem?>? = null
)
data class ResponseBBItem(
#field:SerializedName("birthday")
val birthday: Any? = null,
#field:SerializedName("img")
val img: String? = null,
#field:SerializedName("better_call_saul_appearance")
val betterCallSaulAppearance: Any? = null,
#field:SerializedName("occupation")
val occupation: List<String?>? = null,
#field:SerializedName("appearance")
val appearance: List<Int?>? = null,
#field:SerializedName("portrayed")
val portrayed: String? = null,
#field:SerializedName("name")
val name: String? = null,
#field:SerializedName("nickname")
val nickname: String? = null,
#field:SerializedName("char_id")
val charId: Int? = null,
#field:SerializedName("category")
val category: String? = null,
#field:SerializedName("status")
val status: String? = null
)
Client object
object Client {
val gson = GsonBuilder().create()
val retrofit = Retrofit.Builder()
.baseUrl("https://www.breakingbadapi.com/api/")
.addConverterFactory(GsonConverterFactory.create(gson))
.build()
val api = retrofit.create(APIRequest::class.java)
}
Here's my function to call result in the main activity
class MainActivity : AppCompatActivity() {
private var TAG = "MainActivity"
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
getCharacterInfo()
linearLayout.setOnClickListener {
getCharacterInfo()
}
}
private fun getCharacterInfo() {
GlobalScope.launch(Dispatchers.IO) {
try {
val response = Client.api.getInfo()
if (response.isSuccessful) {
val data = response.body()
Log.d(TAG, data.toString())
withContext(Dispatchers.Main) {
Picasso.get().load(data!!.img).into(ivImage)
tvName.text = data.name
tvOccupation.text = data.toString()
tvActor.text = data.toString()
tvAppearance.text = data.appearance.toString()
tvStatus.text = data.status
}
}
}
catch (e:Exception){
withContext(Dispatchers.Main){
Toast.makeText(applicationContext, "Cannot Load Data" , Toast.LENGTH_LONG).show()
}
}
}
}
}
I see that you try to use coroutines in retrofit, I recommend that you do not work with Response, change it to call and remove the suspend.
interface APIRequest {
#GET("character/random")
fun getInfo() : Call<List<ResponseBB>>
}
In your Global Scope you can call it this way:
GlobalScope.launch {
try{
val response = Client.api.getInfo().await()
}catch(e:Exception){}
}
you can use the version 2.9.0 in retrofit and gson Converter

Android Volley Request utterly fails

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.

Kotlin Json parsing MVVM

I'm trying to learn MVVM architecture on parse Json into a Recyclerview in MVVM using coroutines. But I'm getting error on BlogRepository class.
My Json file looks like this:
[
{
"id": 1,
"name": "potter",
"img": "https://images.example.com/potter.jpg"
},
{ …}
]
I have created data class as below:
#JsonClass(generateAdapter = true)
class ABCCharacters (
#Json(name = "id") val char_id: Int,
#Json(name = "name") val name: String? = null,
#Json(name = "img") val img: String
)
Then the RestApiService as below:
interface RestApiService {
#GET("/api")
fun getPopularBlog(): Deferred<List<ABCCharacters>>
companion object {
fun createCorService(): RestApiService {
val okHttpClient = OkHttpClient.Builder()
.connectTimeout(1, TimeUnit.MINUTES)
.readTimeout(30, TimeUnit.SECONDS)
.writeTimeout(15, TimeUnit.SECONDS)
.build()
return Retrofit.Builder()
.baseUrl("https://example.com")
.addConverterFactory(MoshiConverterFactory.create())
.client(okHttpClient)
.addCallAdapterFactory(CoroutineCallAdapterFactory())
.build().create(RestApiService::class.java)
}
}
}
BlogReposity.kt
class BlogRepository() {
private var character = mutableListOf<ABCCharacters>()
private var mutableLiveData = MutableLiveData<List<ABCCharacters>>()
val completableJob = Job()
private val coroutineScope = CoroutineScope(Dispatchers.IO + completableJob)
private val thisApiCorService by lazy {
RestApiService.createCorService()
}
fun getMutableLiveData():MutableLiveData<List<ABCCharacters>> {
coroutineScope.launch {
val request = thisApiCorService.getPopularBlog()
withContext(Dispatchers.Main) {
try {
val response = request.await()
val mBlogWrapper = response;
if (mBlogWrapper != null && mBlogWrapper.name != null) {
character = mBlogWrapper.name as MutableList<ABCCharacters>
mutableLiveData.value=character;
}
} catch (e: HttpException) {
// Log exception //
} catch (e: Throwable) {
// Log error //)
}
}
}
return mutableLiveData;
}
}
Finally the ViewModel class
class MainViewModel() : ViewModel() {
val characterRepository= BlogRepository()
val allBlog: LiveData<List<ABCCharacters>> get() = characterRepository.getMutableLiveData()
override fun onCleared() {
super.onCleared()
characterRepository.completableJob.cancel()
}
}
I've done this based on https://itnext.io/kotlin-wrapping-your-head-around-livedata-mutablelivedata-coroutine-networking-and-viewmodel-b552c3a74eec
Someone can guide me where am I going wrong & how to fix it?
Your getPopularBlog() API return List<ABCCharacters> instead of ABCCharacters. So, you can't access ABCCharacters's property from your response directly. That's why name property here shows Unresolved reference.
Try below code:
if (mBlogWrapper != null && mBlogWrapper.isNotEmpty()) {
character = mBlogWrapper as MutableList<ABCCharacters>
mutableLiveData.value = character
}
Your response return List but you want to check single object value (mBlogWrapper.name != null). You dont need this line on your code. In the example he checked if "response" is not a "null" and if the blog list is not null. Analize example one more time ;)
if you still have a problem with it, let me know

PostValue didn't update my Observer in MVVM

I have an activity to perform rest API everytime it opened and i use MVVM pattern for this project. But with this snippet code i failed to get updated everytime i open activity. So i debug all my parameters in every line, they all fine the suspect problem might when apiService.readNewsAsync(param1,param2) execute, my postValue did not update my resulRead parameter. There were no crash here, but i got result which not updated from result (postValue). Can someone explain to me why this happened?
Here what activity looks like
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
DataBindingUtil.setContentView<ActivityReadBinding>(this,
R.layout.activity_read).apply {
this.viewModel = readViewModel
this.lifecycleOwner = this#ReadActivity
}
readViewModel.observerRead.observe(this, Observer {
val sukses = it.isSuccess
when{
sukses -> {
val data = it.data as Read
val article = data.article
//Log.d("-->", "${article.toString()}")
}
else -> {
toast("ada error ${it.msg}")
Timber.d("ERROR : ${it.msg}")
}
}
})
readViewModel.getReadNews()
}
Viewmodel
var observerRead = MutableLiveData<AppResponse>()
init {
observerRead = readRepository.observerReadNews()
}
fun getReadNews() {
// kanal and guid i fetch from intent and these value are valid
loadingVisibility = View.VISIBLE
val ok = readRepository.getReadNews(kanal!!, guid!!)
if(ok){
loadingVisibility = View.GONE
}
}
REPOSITORY
class ReadRepositoryImpl private constructor(private val newsdataDao: NewsdataDao) : ReadRepository{
override fun observerReadNews(): MutableLiveData<AppResponse> {
return newsdataDao.resultRead
}
override fun getReadNews(channel: String, guid: Int) = newsdataDao.readNews(channel, guid)
companion object{
#Volatile private var instance: ReadRepositoryImpl? = null
fun getInstance(newsdataDao: NewsdataDao) = instance ?: synchronized(this){
instance ?: ReadRepositoryImpl(newsdataDao).also {
instance = it
}
}
}
}
MODEL / DATA SOURCE
class NewsdataDao {
private val apiService = ApiClient.getClient().create(ApiService::class.java)
var resultRead = MutableLiveData<AppResponse>()
fun readNews(channel: String, guid: Int): Boolean{
GlobalScope.launch {
val response = apiService.readNewsAsync(Constants.API_TOKEN, channel, guid.toString()).await()
when{
response.isSuccessful -> {
val res = response.body()
val appRes = AppResponse(true, "ok", res!!)
resultRead.postValue(appRes)
}
else -> {
val appRes = AppResponse(false, "Error: ${response.message()}", null)
resultRead.postValue(appRes)
}
}
}
return true
}
}
Perhaps this activity is not getting stopped.
Check this out:
When you call readViewModel.getReadNews() in onCreate() your activity is created once, only if onStop is called will it be created again.

Categories

Resources