I want to take data from API using AsyncHttpClient(), after that, I show the data to recycle view. but when trying to debug my code is run show the data to recycle view which is no data because not yet finish setData using AsyncHttpClient(). my question is how to run setData recycle view adapter after my setLeague is finish getting data? Sorry, I am a newbie and I put my code below. Please help, I already try using async-await but doesn't work.
This is my ViewModel Class
val footballLeagueList = MutableLiveData<ArrayList<league>>()
internal fun setLeague(){
val client = AsyncHttpClient()
val client2 = AsyncHttpClient()
val listItems = ArrayList<league>()
val leagueListUrl = "https://www.thesportsdb.com/api/v1/json/1/all_leagues.php"
val leagueDetailUrl = "https://www.thesportsdb.com/api/v1/json/1/lookupleague.php?id="
client.get(leagueListUrl, object : AsyncHttpResponseHandler(){
override fun onSuccess(
statusCode: Int,
headers: Array<out Header>?,
responseBody: ByteArray?
) {
try{
val result = String(responseBody!!)
val responseObject = JSONObject(result)
for(i in 0 until 10){
val list = responseObject.getJSONArray("leagues")
val leagues = list.getJSONObject(i)
val leaguesItems = league()
val detailUrl = leagueDetailUrl + leagues.getString("idLeague")
leaguesItems.id = leagues.getString("id")
leaguesItems.name = leagues.getString("strLeague")
leaguesItems.badgeUrl = leagues.getString( "https://www.thesportsdb.com/images/media/league/badge/dqo6r91549878326.png")
listItems.add(leaguesItems)
}
footballLeagueList.postValue(listItems)
}catch (e: Exception){
Log.d("Exception", e.message.toString())
}
}
override fun onFailure(
statusCode: Int,
headers: Array<out Header>?,
responseBody: ByteArray?,
error: Throwable?
) {
Log.d("Failed", "On Failure")
}
})
}
internal fun getLeague(): LiveData<ArrayList<league>> {
return footballLeagueList
}
And this is my MainActivity
class MainActivity : AppCompatActivity() {
private lateinit var mainViewModel: MainViewModel
private lateinit var adapterLeague: leagueAdapter
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
adapterLeague = leagueAdapter()
adapterLeague.notifyDataSetChanged()
verticalLayout {
lparams(matchParent, matchParent)
recyclerView {
layoutManager = GridLayoutManager(context, 2)
adapter = adapterLeague
}
}
mainViewModel = ViewModelProvider(this, ViewModelProvider.NewInstanceFactory())
.get(MainViewModel::class.java)
mainViewModel.setLeague()
mainViewModel.getLeague().observe(this, Observer { leaguesItems ->
if(leaguesItems != null){
adapterLeague.setData(leaguesItems)
}
})
}
}
Related
I am currently building a NewsApplication consisting of 7 different categories
The problem I am currently facing is, whenever I start the app, the app would send out 7 requests, however at times, some of the responses would result in the Sockettimeout error, which makes it awkward as some of the Fragments will be populated while the others will be blank.
I then tried a different method, I attempted to prevent any of the fragments are loading untill all of the responses are successful, however that will just leave me with a blank/Loading screen when the Sockettimeout error occurs.
I am trying the find a way to block any fragments from loading when there is a Sockettimeout error and display the relevent Error Message.
Repository, I used the Callback interface to help me detect server side errors such as SocketTimeOutExeption
class NewsRepository(val db:RoomDatabases ) {
suspend fun upsert(article: Article) = db.getArticleDao().upsert(article)
fun getSavedNews() = db.getArticleDao().getAllArticles()
suspend fun deleteArticle(article: Article) = db.getArticleDao().deleteArticle(article)
suspend fun empty() = db.getArticleDao().isEmpty()
suspend fun nukeTable() = db.getArticleDao().nukeTable()
fun getNewsCall(country: String, Category: String?): MutableLiveData<MutableList<Article>> {
val call = RetrofitHelper.NewsApiCall.api.getNews(
country,
Category,
"5a3e054de1834138a2fbc4a75ee69053"
)
var Newlist = MutableLiveData<MutableList<Article>>()
call.enqueue(object : Callback<NewsDataFromJson> {
override fun onResponse(
call: Call<NewsDataFromJson>,
response: Response<NewsDataFromJson>
) {
if (response.isSuccessful) {
val body = response.body()
if (body != null) {
Newlist.value = body.articles
}
} else {
val jsonObj: JSONObject?
jsonObj = response.errorBody()?.string().let { JSONObject(it) }
if (jsonObj != null) {
MainActivity.apiRequestError = true
MainActivity.errorMessage = jsonObj.getString("message")
Newlist.value = mutableListOf<Article>()
}
}
}
override fun onFailure(call: Call<NewsDataFromJson>, t: Throwable) {
MainActivity.apiRequestError = true
MainActivity.errorMessage = t.localizedMessage as String
Log.d("err_msg", "msg" + t.localizedMessage)
}
})
return Newlist
}
}
MainActivity, this is where I call the requests
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
requestNews(GENERAL, generalNews,"us")
requestNews(TECHNOLOGY,TechNews,"us")
requestNews(HEALTH,healthNews,"us")
requestNews(SPORTS, SportsNews,"us")
requestNews(ENTERTAINMENT, EntertainmentNews,"us")
requestNews(SCIENCE, ScienceNews,"us")
requestNews(BUSINESS, BusinessNews,"us")
}
private fun requestNews(newsCategory: String, newsData: MutableList<Article>,country:String) {
viewModel.getNews(category = newsCategory, Country = country)?.observe(this) {
newsData.addAll(it)
totalRequestCount += 1
if(!apiRequestError){
if(totalRequestCount == 7){
ProgresBar.visibility = View.GONE
ProgresBar.visibility = View.GONE
setViewPager()
}
}else if(apiRequestError){
ProgresBar.visibility = View.GONE
FragmentContainer.visibility = View.GONE
val showError: TextView = findViewById(R.id.display_error)
showError.text = errorMessage
showError.visibility = View.VISIBLE
}
}
}
companion object{
var ScienceNews: MutableList<Article> = mutableListOf()
var EntertainmentNews: MutableList<Article> = mutableListOf()
var SportsNews: MutableList<Article> = mutableListOf()
var BusinessNews: MutableList<Article> = mutableListOf()
var healthNews: MutableList<Article> = mutableListOf()
var generalNews: MutableList<Article> = mutableListOf()
var TechNews: MutableList<Article> = mutableListOf()
var apiRequestError = false
var errorMessage = "error"
var SocketTimeout: JSONException? = null
}
}
ViewPagingFragment, this is where the ViewPager lives and this is where the FragmentAdapter is connected to.
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
val Categories = arrayListOf<String>("BreakingNews","Technology","Health","Science","Entertainment","Sports","Business")
viewpager(Categories)
viewPagerView = view.findViewById(R.id.view_pager)
viewPagerView.offscreenPageLimit = 7
var MainToolbarSaved = requireActivity().findViewById<Toolbar>(R.id.MenuToolBar)
var SecondaryToolBarSaved = requireActivity().findViewById<Toolbar>(R.id.topAppBarthesecond)
var MenuSavedButton = requireActivity().findViewById<ImageButton>(R.id.MenuSavedButton)
MainToolbarSaved.visibility = View.VISIBLE
SecondaryToolBarSaved.visibility = View.GONE
MenuSavedButton.setOnClickListener {
this.findNavController().navigate(R.id.action_global_savedFragment)
}
}
fun viewpager(FragmentList:ArrayList<String>){
val tabLayout = binding.tabLayout
PagerAdapter = FragmentAdapter(childFragmentManager,lifecycle)
binding.viewPager.adapter = PagerAdapter
tabLayout.tabMode = TabLayout.MODE_SCROLLABLE
TabLayoutMediator(tabLayout, binding.viewPager) { tab, position ->
tab.text = FragmentList[position]
}.attach()
}
Any tips on how I can do this?
I am currently building a NewsApplication consisting of 7 different categories
App when working properly
The problem I am currently facing is, whenever I start the app, the app would send out 7 requests, however at times, some of the responses would result in the Sockettimeout error, which makes it awkward as some of the Fragments will be populated while the others will be blank.
I then tried a different method, I attempted to prevent any of the fragments from loading untill all of the responses are successful, however that will just leave me with a blank/Loading screen when one of the resonses suffer from a Sockettimeout error occurs.
**
Is there any way to force the app from displaying anything except for the error message when any of the responses suffer from an error?**
App when there is an error, like no internet connection or Sockettimeouterror
I am trying the find a way to block any fragments from loading when there is a Sockettimeout error and display the relevent Error Message.
Repository, I used the Callback interface to help me detect server side errors such as SocketTimeOutExeption
class NewsRepository(val db:RoomDatabases ) {
suspend fun upsert(article: Article) = db.getArticleDao().upsert(article)
fun getSavedNews() = db.getArticleDao().getAllArticles()
suspend fun deleteArticle(article: Article) = db.getArticleDao().deleteArticle(article)
suspend fun empty() = db.getArticleDao().isEmpty()
suspend fun nukeTable() = db.getArticleDao().nukeTable()
fun getNewsCall(country: String, Category: String?): MutableLiveData<MutableList<Article>> {
val call = RetrofitHelper.NewsApiCall.api.getNews(
country,
Category,
"5a3e054de1834138a2fbc4a75ee69053"
)
var Newlist = MutableLiveData<MutableList<Article>>()
call.enqueue(object : Callback<NewsDataFromJson> {
override fun onResponse(
call: Call<NewsDataFromJson>,
response: Response<NewsDataFromJson>
) {
if (response.isSuccessful) {
val body = response.body()
if (body != null) {
Newlist.value = body.articles
}
} else {
val jsonObj: JSONObject?
jsonObj = response.errorBody()?.string().let { JSONObject(it) }
if (jsonObj != null) {
MainActivity.apiRequestError = true
MainActivity.errorMessage = jsonObj.getString("message")
Newlist.value = mutableListOf<Article>()
}
}
}
override fun onFailure(call: Call<NewsDataFromJson>, t: Throwable) {
MainActivity.apiRequestError = true
MainActivity.errorMessage = t.localizedMessage as String
Log.d("err_msg", "msg" + t.localizedMessage)
}
})
return Newlist
}
}
MainActivity, this is where I call the requests
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
requestNews(GENERAL, generalNews,"us")
requestNews(TECHNOLOGY,TechNews,"us")
requestNews(HEALTH,healthNews,"us")
requestNews(SPORTS, SportsNews,"us")
requestNews(ENTERTAINMENT, EntertainmentNews,"us")
requestNews(SCIENCE, ScienceNews,"us")
requestNews(BUSINESS, BusinessNews,"us")
}
private fun requestNews(newsCategory: String, newsData: MutableList<Article>,country:String) {
viewModel.getNews(category = newsCategory, Country = country)?.observe(this) {
newsData.addAll(it)
totalRequestCount += 1
if(!apiRequestError){
if(totalRequestCount == 7){
ProgresBar.visibility = View.GONE
ProgresBar.visibility = View.GONE
setViewPager()
}
}else if(apiRequestError){
ProgresBar.visibility = View.GONE
FragmentContainer.visibility = View.GONE
val showError: TextView = findViewById(R.id.display_error)
showError.text = errorMessage
showError.visibility = View.VISIBLE
}
}
}
companion object{
var ScienceNews: MutableList<Article> = mutableListOf()
var EntertainmentNews: MutableList<Article> = mutableListOf()
var SportsNews: MutableList<Article> = mutableListOf()
var BusinessNews: MutableList<Article> = mutableListOf()
var healthNews: MutableList<Article> = mutableListOf()
var generalNews: MutableList<Article> = mutableListOf()
var TechNews: MutableList<Article> = mutableListOf()
var apiRequestError = false
var errorMessage = "error"
var SocketTimeout: JSONException? = null
}
}
ViewPagingFragment, this is where the ViewPager lives and this is where the FragmentAdapter is connected to.
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
val Categories = arrayListOf<String>("BreakingNews","Technology","Health","Science","Entertainment","Sports","Business")
viewpager(Categories)
viewPagerView = view.findViewById(R.id.view_pager)
viewPagerView.offscreenPageLimit = 7
var MainToolbarSaved = requireActivity().findViewById<Toolbar>(R.id.MenuToolBar)
var SecondaryToolBarSaved = requireActivity().findViewById<Toolbar>(R.id.topAppBarthesecond)
var MenuSavedButton = requireActivity().findViewById<ImageButton>(R.id.MenuSavedButton)
MainToolbarSaved.visibility = View.VISIBLE
SecondaryToolBarSaved.visibility = View.GONE
MenuSavedButton.setOnClickListener {
this.findNavController().navigate(R.id.action_global_savedFragment)
}
}
fun viewpager(FragmentList:ArrayList<String>){
val tabLayout = binding.tabLayout
PagerAdapter = FragmentAdapter(childFragmentManager,lifecycle)
binding.viewPager.adapter = PagerAdapter
tabLayout.tabMode = TabLayout.MODE_SCROLLABLE
TabLayoutMediator(tabLayout, binding.viewPager) { tab, position ->
tab.text = FragmentList[position]
}.attach()
}
Any tips on how I can do this?
I have attempted to look through other people's project and looked through the documentations for viewpager just to name a few.
I try to write an quiz app for Android in Kotlin.
I take all data from API and create data classes
Quiz
data class Quiz(
val answerIds: List<Any>,
val groupCodes: List<Any>,
val questionList: List<Question>
)
Question
data class Question(
val answers: List<Answer>,
val groupCode: String,
val hasSimilarQuestions: Boolean,
val id: Int,
val text: String
)
Answer
data class Answer(
val addsGroupCodes: List<String>,
val id: Int,
val questionId: Int,
val text: String
)
I use Volley to make http request
My problem is:
I try to display only text from answers to specific question in ListView.
I create ArrayAdapter but I can not display only text of certain answer
class MainActivity : AppCompatActivity()
{
override fun onCreate(savedInstanceState: Bundle?)
{
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val url = ""
showQuiz(url);
}
private fun showQuiz(url:String)
{
val requestQueue = Volley.newRequestQueue(this)
val pytanie : TextView = findViewById(R.id.pytanie)
val jsonObjectRequest = JsonObjectRequest(Request.Method.GET, url, null, object : Response.Listener<JSONObject?>
{
override fun onResponse(response: JSONObject?) {
try
{
val jsonArray = response?.getJSONArray("questionList")
if (jsonArray != null)
{
val quiz:Quiz = Gson().fromJson(response.toString(), Quiz::class.java)
val odp = findViewById<ListView>(R.id.odpowiedzi)
for (question in quiz.questionList)
{
val arrayAdapter: ArrayAdapter<Answer> = ArrayAdapter(this#MainActivity, android.R.layout.simple_list_item_1, question.answers)
odp.adapter = arrayAdapter
}
}
} catch (e: JSONException) {
e.printStackTrace()
}
}
},
object : Response.ErrorListener
{
override fun onErrorResponse(error: VolleyError)
{
error.printStackTrace()
Log.d("JsonObjectErr",error.toString());
}
}
)
requestQueue.add(jsonObjectRequest)
}
}
What about access to other elements in ArrayAdapter, because I need to remember what user choose.
ArrayAdapter shows value from toString() of an item.
So override toString() in Answer and return only value you want to show.
I have API data which ID's are uuid (strings) and not (integer) and when I want to get those ids in my adapter it says
Type mismatch.
Required:
Int
Found:
String?
Sample of API items
{
"id":"0ade1bfb-6d02-4a1f-9cd4-dc88fa8aadbd",
"name":"ABC",
"photo":null // if not null, will be full URL of image (https://example.com/img/abc.jpg)
}
Code
Adapter (commented)
class ServicesAdapter(private val serviceList: List<Service>) : RecyclerView.Adapter<ServicesAdapter.ServiceViewHolder>() {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ServiceViewHolder {
val imageView =
LayoutInflater.from(parent.context).inflate(R.layout.service_item, parent, false)
return ServiceViewHolder(imageView)
}
override fun onBindViewHolder(holder: ServiceViewHolder, position: Int) {
val currentItem = serviceList[position]
holder.imageView.setImageResource(currentItem.photo) <-- error line
holder.textView.text = currentItem.name
}
override fun getItemCount() = serviceList.size
class ServiceViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
val imageView: ImageView = itemView.imageView
val textView: TextView = itemView.textView2
}
}
Activity
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
run("api_url")
}
fun run(url: String) {
val request = Request.Builder()
.url(url)
.build()
client.newCall(request).enqueue(object : Callback {
override fun onFailure(call: Call, e: IOException) {}
override fun onResponse(call: Call, response: Response) {
val list: ArrayList<Service> = ArrayList()
getServices(response.body()!!.string(), list)
recycler.layoutManager = LinearLayoutManager(this#MainActivity)
recycler.adapter = ServicesAdapter(list)
}
})
}
fun getServices(response: String, list: ArrayList<Service>) {
var jsonObject = JSONObject(response)
val jsonArray = jsonObject.getJSONArray("data")
for (i in 0 until jsonArray.length()) {
val jsonObject1 = jsonArray.getJSONObject(i)
var listingObject = Service(
jsonObject1.getString("id"),
jsonObject1.getString("name"),
jsonObject1.getString("photo")
)
list.add(listingObject)
}
}
Class
class Service (val id: String?, val name: String?, val photo: String?) {
}
Any idea?
Add the following lines of code in your OnBindViewHolder to load images from the URL
currentItem.photo?.apply{
Glide.with(holder.imageView.context)
.load(this)
.into(holder.imageView)
}
holder.imageView.setImageResource(currentItem.photo)
this method requires int value, which you are providing null(treated as null string)
try replacing null as blank while parsing json data
or you can use this
public static boolean isBlankOrNull(String value)
{
return (value == null || value.equals("") || value.equals("null") || value.trim().equals(""));
}
like this
holder.imageView.setImageResource(isBlankOrNull(currentItem.photo) ? 0 : currentItem.photo)
Just set image when you are getting it in response i.e. its value is not empty or null.Also you need to use any library to set image to imageview for example You can use Square's Picasso and can update your code as below
if(!TextUtils.isEmpty(currentItem.photo))
Picasso
.get()
.load(currentItem.photo)
.into(holder.imageView)
I'm trying to make my Android App (I'm only experienced in iOS).
I created a RecyclerView that gets the data from a web. I tried everything to implement endless scrolling to load more items, but when I call the function to get the items, the entire RecyclerView loads again and no attach the new results on the bottom.
This is my code:
ConversationUser.kt
data class ConversationUser(
val message_nickname: String,
val message_image_thumb: String,
val message_large_thumb: String,
val message_modified: String,
val message_status: String,
val message_unread: Int,
val conv_id: String,
val message_dest: String) {
}
ConversacionesActivity.kt
class ConversacionesActivity : AppCompatActivity() {
// MARK: Variables
var user_token = ""
var user_id = ""
override fun onCreate(savedInstanceState: Bundle?) {
// User Defaults
val sharedPreferences = getSharedPreferences("Preferences", Context.MODE_PRIVATE)
user_token = sharedPreferences.getString("user_token", "")!!
user_id = sharedPreferences.getString("user_id", "")!!
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_conversaciones)
recyclerConv.addItemDecoration(DividerItemDecoration(this, LinearLayoutManager.VERTICAL))
getConversationsData()
recyclerConv.setLoadingListener(object : LoadingListener {
override fun onRefresh() {
//refresh data here
}
override fun onLoadMore() {
// load more data here
getConversationsData()
}
})
}
fun getConversationsData() {
val httpAsync = "https://mywebsite.com/conversations/${user_token}"
.httpPost()
.responseString { request, response, result ->
when (result) {
is Result.Failure -> {
val ex = result.getException()
println(ex)
}
is Result.Success -> {
val data = result.get()
runOnUiThread {
val conversaciones = processJson(data)
show(conversaciones)
return#runOnUiThread
}
}
}
}
httpAsync.join()
}
fun processJson(json: String): List<ConversationUser> {
val gson: Gson = GsonBuilder().create()
val conversaciones: List<ConversationUser> = gson.fromJson(
json,
Array<ConversationUser>::class.java
).toList()
return conversaciones
}
fun show(conversaciones: List<ConversationUser>) {
recyclerConv.layoutManager = LinearLayoutManager(this, LinearLayoutManager.VERTICAL, false)
recyclerConv.adapter = AdaptadorConv(conversaciones, this, user_token, user_id)
}
AdaptadorConv.kt
class AdaptadorConv(
val conversaciones: List<ConversationUser> = ArrayList(),
val context: Context,
val user_token: String,
val user_id: String) : RecyclerView.Adapter<AdaptadorConv.ConvViewHolder>() {
override fun onBindViewHolder(holder: ConvViewHolder, position: Int) {
holder.convName.text = conversaciones[position].message_nickname
holder.convTime.text = conversaciones[position].message_modified
}
override fun getItemCount(): Int {
return conversaciones.size - 1
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ConvViewHolder {
val view: View = LayoutInflater.from(parent.context).inflate(
R.layout.conversaciones,
parent,
false
)
return ConvViewHolder(view)
}
class ConvViewHolder(vista: View): RecyclerView.ViewHolder(vista) {
val convImg: ImageView = itemView.findViewById(R.id.convImg)
val convStatus: ImageView = itemView.findViewById(R.id.convStatus)
val convName: TextView = itemView.findViewById(R.id.convName)
val convUnread: TextView = itemView.findViewById(R.id.convUnread)
val convTime: TextView = itemView.findViewById(R.id.convTime)
}
Thanks for any help or hint.
Please check your show () method, you are creating new Adapter every time with the new dataset. You have to append the new items to the adapter's list and adapter should be set to list once. Helpful tutorial can be found at here.