Fragment not attached to a context; requireContext() in ArrayAdapter, Async Task - android

Can't figure out what I have to do, to create a ListView by using ArrayAdapter. So far here's my code:
class GetSongListAsync(private val activity: LastSongs) : AsyncTask<Void, Void, Array<String>>() {
override fun doInBackground(vararg params: Void): Array<String> {
val url =
URL("https://vivalaresistance.ru/radio/stuff/vlrradiobot.php?type=getPlaylist")
val connection = url.openConnection() as HttpURLConnection
connection.requestMethod = "GET"
connection.connect()
val songList = BufferedReader(InputStreamReader(connection.inputStream, "windows-1251"))
connection.disconnect()
return songList
.readText()
.replace("й", "й")
.replace("Й", "Й")
.split("\n").dropLast(1).toTypedArray()
}
override fun onPostExecute(result: Array<String>) {
super.onPostExecute(result)
val list = LastSongs().view?.findViewById<ListView>(R.id.list)
val arrayAdapter = ArrayAdapter(activity.requireContext(), android.R.layout.simple_list_item_1, result)
list?.adapter = arrayAdapter
}
}
Right now the app is crashing everytime I'm switching to LastSongs fragment and I'm getting java.lang.IllegalStateException: Fragment LastSongs{79211d9} (2b2b2711-f9a8-4352-92ec-55a25a224ea0) not attached to a context.
If I comment three lines and try to Log the result, everything is fine. The result is exactly what i need.
override fun onPostExecute(result: Array<String>) {
super.onPostExecute(result)
Log.d("Songs", result.joinToString("; "))
// val list = LastSongs().view?.findViewById<ListView>(R.id.list)
// val arrayAdapter = ArrayAdapter(activity.requireContext(), android.R.layout.simple_list_item_1, result)
// list?.adapter = arrayAdapter
}
So far I find, that the problem is with activity.requireContext(). I'm making a mistake that I can't understand. How can I get the right context here?

Looks like your code isn't providing the correct params, if you are using the androidx.fragment.app.Fragment you can simply call requireContext() or requireActivity()
private View view;
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
view = inflater.inflate(R.layout.fragment_page, container, false);
return view;
}
val list = view?.findViewById<ListView>(R.id.list)
val arrayAdapter = ArrayAdapter(view.getContext(), android.R.layout.simple_list_item_1, counties)```
Please check your imports.

I finally fixed it. I added two parameters to my GetSongListAsync class: one for context and one for list. Now it looks like this:
class GetSongListAsync(private val context: Context, private val list: ListView) : AsyncTask<Void, Void, Array<String>>() {
override fun doInBackground(vararg params: Void): Array<String> {
*stuff*
}
override fun onPostExecute(result: Array<String>) {
super.onPostExecute(result)
val arrayAdapter = ArrayAdapter(context, android.R.layout.simple_list_item_1, result)
list.adapter = arrayAdapter
}
}
Now every time I'm calling this class from view, I have to add this parameters.
val reqCon = requireContext()
val swipe: SwipeRefreshLayout = view.findViewById(R.id.swipe)
val list = view.findViewById<ListView>(R.id.list)
swipe.setOnRefreshListener {
GetSongListAsync(reqCon, list).execute()
swipe.isRefreshing = false
}
Maybe not the best way to do it, but it works.

Related

Custom adapter in coroutine

When I try to fill a ListView using a custom adapter, I get an empty list, I can't figure out what the error is? Why is everything loaded and displayed when using the default adapter?
HeroesAdapter.kt
class HeroesAdapter(context: Context, heroes: List<TestHero>): BaseAdapter() {
private val context = context
private val heroes = heroes
override fun getCount(): Int {
return heroes.count()
}
override fun getItem(position: Int): Any {
return heroes[position]
}
override fun getItemId(position: Int): Long {
return 0
}
override fun getView(position: Int, convertView: View?, parent: ViewGroup?): View {
// categoryView = LayoutInflater.from(context).inflate(R.layout.activity_heroes, null)
val categoryView = LayoutInflater.from(context).inflate(R.layout.activity_heroes, parent, false)
// val categoryImage: ImageView = categoryView.findViewById(R.id.heroesImageView)
val categoryText: TextView = categoryView.findViewById(R.id.textHeroView)
val category = heroes[position]
categoryText.text = category.global.name
return categoryView
}
}
HeroesActivity
class HeroesActivity : AppCompatActivity() {
lateinit var adapter : ArrayAdapter<TestHero>
lateinit var adapt: ArrayAdapter<String>
lateinit var heroesAdapt : HeroesAdapter
var listHero = ArrayList<TestHero>()
private val TAG = "HeroesActivity"
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_heroes)
// val adapterr = ArrayAdapter(this, android.R.layout.simple_list_item_1,)
adapter = ArrayAdapter(this, android.R.layout.simple_list_item_1,
LinkedList<TestHero>())
// heroesListView.adapter = adapter
adapt = ArrayAdapter(this, android.R.layout.simple_list_item_1,
LinkedList<String>())
//heroesListView.adapter = adapt
getCurrentData()
heroesAdapt = HeroesAdapter(this, listHero)
heroesListView.adapter = heroesAdapt
}
private fun getCurrentData() {
val api = Retrofit.Builder()
.baseUrl(BASE_URL)
.addConverterFactory(GsonConverterFactory.create())
.build()
.create(ApiRequest::class.java)
GlobalScope.launch(Dispatchers.IO) {
val response = api.herList().awaitResponse()
if (response.isSuccessful) {
val data = response.body()!!
Log.d(TAG, data.toString())
withContext(Dispatchers.Main){
// adapter.add(data.global.name)
adapt.add(data.global.platform)
listHero.add(data)
}
}
}
}
}
Looks like you add data to the backing list of the adapter (listHero.add(data)), but you never inform the adapter that its backing data has changed (heroesAdapt.notifyDataSetChanged()).
As an aside, there are some issues with your coroutine. You should not use GlobalScope. Use lifecycleScope instead to avoid leaking network calls and copies of your Activity. Really, you should fetch data in a ViewModel using the ViewModel's scope and expose it via LiveData or SharedFlow so the network call doesn't have to restart if the phone rotates.

mvp recycler adapter not showing data

The problem I am facing with the RecyclerView is the data is coming from Server and the API response is getting printed correctly in the console.
but when I am trying to set data in the adapter what is wrong or something is not going correctly with the flow that the data is not being updated on UI.
//This is my adapter class
class DashboardAdapter(val context: Context) : RecyclerView.Adapter<DashBoardHolder>() {
private var transactionList = ArrayList<DashboardData>()
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): DashBoardHolder {
val inflator = LayoutInflater.from(parent.context)
val view = ActivityDashboardDataBinding.inflate(inflator, parent, false)
val viewHolder = DashBoardHolder(view)
return viewHolder
}
override fun onBindViewHolder(holder: DashBoardHolder, position: Int) {
val quickModel = transactionList[position]
holder.tvName.text = quickModel.bookingTitle
}
fun showListItems(dashboardlist: List<DashboardData>?, aboolean: Boolean) {
when {
aboolean -> transactionList.clear()
}
if (dashboardlist != null && !dashboardlist.isEmpty())
this.transactionList.addAll(dashboardlist)
notifyDataSetChanged()
}
override fun getItemCount(): Int {
return transactionList.size
}
}
MyHolderClass
class DashBoardHolder(val binding: ActivityDashboardDataBinding) :
RecyclerView.ViewHolder(binding.root) {
var tvName: TextView = binding.textViewGrandrukName
var tvTime: TextView = binding.tvGrandrukTripDetails
var tvPlace: ImageView = binding.btnGhandruk
var ivRectangle: ImageView = binding.imageView5
}
Similarly,I set the adapter in view section like this way:
//setting adapter in Presenter class
fun setAdapter() {
var layoutmanager: LinearLayoutManager? = LinearLayoutManager(appCompatActivity)
val firstVisiblePosition = layoutmanager!!.findFirstVisibleItemPosition()
binding!!.includesDashboardRecyclerview.rvBookingList.setHasFixedSize(true)
binding!!.includesDashboardRecyclerview.rvBookingList.layoutManager = layoutmanager
binding!!.includesDashboardRecyclerview.rvBookingList.adapter = dashboardAdapter
layoutmanager!!.scrollToPositionWithOffset(firstVisiblePosition, 0)
}
In Presenter Class, calling setAdapter class from presenter like this way
class DashboardPresenter(
private val dashboardView: DashboardView,
private val dashboardModel: DashboardModel
) {
fun onCreateView() {
onClick()
dashboardView.setAdapter()
getDashboardRequest()
}
//calling adpter function here
fun showList(termlist: List<DashboardData>?, aboolean: Boolean) {
(null as DashboardAdapter?)?.showListItems(termlist!!, aboolean)
}
}
I'm not able to understand what is getting wrong here.
(null as DashboardAdapter?)?.showListItems(termlist!!, aboolean)
You are calling showListItems() on the DashboardAdapter as a type not the instance dashboardAdapter. Assuming that dashboardAdapter is a local class field.
Also I guess this type casting is not necessary as you already using the optional ?
So, it can be simplified to:
dashboardAdapter?.showListItems(termlist!!, aboolean)
Assuming that this should be called whenever you retrieve the API response. So, showList() must be called when there's new API data.

Trouble Using Edit Search Function in Recycler View with Cards

I am trying to search through a recycler view with cards by allowing a user to search. When the user searches, the cards should "reorganize" to show according to the characters entered by the user. I have tried to do this but am having issues doing this. Any assistance is appreciated.
MainActivity.kt
class MainActivity : AppCompatActivity(), BottomSheetRecyclerViewAdapter.ListTappedListener {
private var customAdapter: CustomAdapter? = null
private var arrayListModel = ArrayList<Model>()
private lateinit var bottomSheetBehavior: CustomBottomSheetBehavior<ConstraintLayout>
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val modelList = readFromAsset()
val adapterList = CustomAdapter(modelList, this)
customAdapter = CustomAdapter(arrayListModel, this#MainActivity)
bottomSheetBehavior = BottomSheetBehavior.from(bottomSheetLayout) as CustomBottomSheetBehavior
recyclerView.adapter = adapterList
recyclerView.layoutManager = LinearLayoutManager(this, RecyclerView.VERTICAL, false)
et_search.addTextChangedListener(object : TextWatcher {
override fun afterTextChanged(s: Editable?) {
}
override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {
}
override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {
if (!s.isNullOrEmpty()) {
val searchList = ArrayList<Model>()
for (i in arrayListModel.indices) {
if (arrayListModel[i].name.toLowerCase().contains(s)) {
searchList.add(arrayListModel[i])
}
}
try {
customAdapter?.notifyDataSetChanged()
recyclerView.adapter = CustomAdapter(searchList, this#MainActivity)
} catch (e: Exception) {
e.printStackTrace()
}
} else {
customAdapter?.notifyDataSetChanged()
recyclerView.adapter = customAdapter
}
}
})
}
override fun onClickList(text: String) {
}
private fun readFromAsset(): List<Model> {
val modeList = mutableListOf<Model>()
val bufferReader = application.assets.open("android_version.json").bufferedReader()
val json_string = bufferReader.use {
it.readText()
}
val jsonArray = JSONArray(json_string);
for (i in 0..jsonArray.length() - 1) {
val jsonObject: JSONObject = jsonArray.getJSONObject(i)
val model = Model(jsonObject.getString("name"), jsonObject.getString("version"))
modeList.add(model)
}
return modeList
}
}
I might found your problem. Here you getting data val modelList = readFromAsset() but you are never assigning data to arrayListModel that your problem.
Assign the data to arrayListModel
val modelList = readFromAsset()
arrayListModel=modelList
Here's a clean approach you might want to consider/try:
Make your adapter implement the filterable interface.
Provide your own Filter object in which you implement your filtering logic (asynch).
You might as well use a SearchView instead of using onTextChange on an EditText.
So: onTextChange(newText) => call adapter.getFilter().filter(newText) => filtering happens in background (filter method performFiltering is called) => when filtered list ready (filter method publishResults is called), you push it to your adapter and notifyDataSetChanged.
Hope this helps.
Here's a clean example on how to implement this:
Example

Kotlin: No adapter attached; skipping layout :RecyclerView

I'm trying to implement Recyclerview in my kotlin code....
And I'm using Retrofit getting data from webservice and plot it into recycler view
MainActivity.class
class MainActivity : AppCompatActivity() {
internal lateinit var jsonApi:MyAPI
private val compositeDisposable = CompositeDisposable()
lateinit var recyclerView: RecyclerView
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
recyclerView = findViewById(R.id.recycler_drivers)
// init API
val retrofitt = RetrofitClient.instance
if (retrofitt != null) {
jsonApi = retrofitt.create(MyAPI::class.java)
}
//View
recyclerView.setHasFixedSize(true)
recyclerView.layoutManager = LinearLayoutManager(this)
fetchData()
}
private fun fetchData() {
compositeDisposable.add(jsonApi.drivers
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe{drivers->displayData(drivers)}
)
}
private fun displayData(drivers: List<Driver>?) {
val adapter = DriverAdapter(this,drivers!!)
recycler_drivers.adapter = adapter
}
}
Adapter.class
class DriverAdapter(internal var contex:Context, internal var driverList:List<Driver>): RecyclerView.Adapter<DriverViewHolder>()
{
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): DriverViewHolder {
val itemView = LayoutInflater.from(parent.context)
.inflate(R.layout.driver_layout, parent, false)
return DriverViewHolder(itemView)
}
override fun getItemCount(): Int {
return driverList.size
}
override fun onBindViewHolder(holder: DriverViewHolder, position: Int) {
holder.txt_driver_number.text = driverList[position].driver_number
holder.txt_first_name.text = driverList[position].first_name
holder.txt_ph_number.text = driverList[position].ph_number.toString()
}
}
ViewHolder.class
class DriverViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
val txt_driver_number = itemView.txt_driver_number
val txt_first_name = itemView.txt_first_name
val txt_ph_number = itemView.txt_ph_number
}
This is the API interface
interface MyAPI {
#get:GET("data")
val drivers:Observable<List<Driver>>
}
RetrofitClient Object
object RetrofitClient {
private var ourInstance : Retrofit? = null
var instance: Retrofit? = null
get(){
if(ourInstance == null){
ourInstance = Retrofit.Builder()
.baseUrl("http://localhost/BSProject/public/")
.addConverterFactory(GsonConverterFactory.create())
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
.build()
}
return ourInstance!!
}
}
and this is the Model class which is basically the data coming form my localhost server
class Driver {
var driver_number: String = ""
var first_name: String = ""
var ph_number: Int = 0
}
As you can see I have attached an adapter for Recycleview. so why do I keep getting this error?
I have read other questions related to same problem, but none helps.
Either build the recyclerView inside your displayData()
private fun displayData(drivers: MutableList<Driver>?) {
recyclerView.setHasFixedSize(true)
recyclerView.layoutManager = LinearLayoutManager(this)
val adapter = DriverAdapter(this,drivers!!)
recycler_drivers.adapter = adapter
}
Or do what Gabriele Suggested where you attach your adapter to the recyclerviewin onCreate() and add your response data to your adapter after having made the call. This is the ideal approach
class MainActivity: {
lateinit var driverAdapter: DriverAdapter
protected void onCreate() {
...
recyclerView = findViewById(R.id.recycler_drivers)
recyclerView.setHasFixedSize(true)
recyclerView.layoutManager = LinearLayoutManager(this)
val adapter = DriverAdapter(this)
recycler_drivers.adapter = adapter
}
private fun displayData(drivers: List<Driver>?) {
driverAdapter.setDrivers(drivers)
}
And you'd expose a method in your adapter to set the data setDrivers()
class DriverAdapter(internal var contex:Context):
RecyclerView.Adapter<DriverViewHolder>()
{
val drivers = mutableListOf()
...
fun setDrivers(drivers: MutableList<Driver>) {
this.drivers = drivers
notifyDataSetChanged()
}
}
This will get rid of your No adapter attached; skipping layout :RecyclerView error
I think you are seeing this issue because of the asynchronous nature of querying the web service through retrofit. You don't actually assign the RecyclerView.Adapter until after onCreate exits.
Try changing the visiblility of the RecyclerView to Gone until the adapter is applied in displayData, then set it to Visible

Android: cannot refresh Listview using CustomAdapter

I need to refresh the list view with new data. This code below is used to obtain data in OnCreateView that is in FragmentActivity at the first time.
override fun onCreateView(inflater: LayoutInflater?, container: ViewGroup?,
savedInstanceState: Bundle?): View? {
val url = "something"
val request_queue = Volley.newRequestQueue(this.context)
val stringRequest = StringRequest(Request.Method.GET, url,
Response.Listener<String> { response ->
val pending_job = Gson().fromJson<ArrayList<HashMap<String, String>>>(
response.toString(),
object : TypeToken<ArrayList<HashMap<String, String>>>() {}.type
)
this#PlaceholderFragment.showList(rootView, R.id.pending_job_list, pending_job)
pending_job_list_layout.setOnRefreshListener(this)
request_queue.stop()
}, Response.ErrorListener { error ->
if (error.networkResponse != null) {
Toast.makeText(this.context, error.networkResponse.statusCode.toString(), Toast.LENGTH_SHORT).show()
}
request_queue.stop()
})
request_queue.add(stringRequest)
}
showList is the function to set CustomAdapter, which is
fun showList(rootView: View, tab_id: Int, job_list: ArrayList<HashMap<String, String>>) {
// custom display ListView
val adapter: CustomAdapter = CustomAdapter(
this.context,
job_list
)
this_adapter = adapter
val listView = rootView.findViewById(tab_id) as ListView
listView.adapter = adapter
}
, and this CustomAdapter class,
class CustomAdapter(internal var mContext: Context,
internal var job_list: ArrayList<HashMap<String, String>>
) : BaseAdapter() {
override fun getCount(): Int {
return job_list.size
}
override fun getItem(position: Int): Any? {
return null
}
override fun getItemId(position: Int): Long {
return 0
}
override fun getView(position: Int, view: View?, parent: ViewGroup): View {
val mInflater = mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE) as LayoutInflater
val item_view = mInflater.inflate(R.layout.activity_job_item, parent, false)
val job_name: MutableList<String> = arrayListOf()
job_list.mapTo(job_name) { it["BaseSiteName"] as String }
val nameView: TextView = item_view.findViewById(R.id.name) as TextView
nameView.text = job_name[position]
val job_date: MutableList<String> = arrayListOf()
job_list.mapTo(job_date) { it["PlanDt"] as String}
val dateView: TextView = item_view.findViewById(R.id.date) as TextView
dateView.text = job_date[position]
item_view.setOnClickListener{
val intent: Intent = Intent(mContext, JobDetailActivity::class.java)
intent.putExtra("job", job_list[position])
mContext.startActivity(intent)
}
return item_view
}
}
However, I want to make the list refresh-able. I've written the following code to refresh the list.
override fun onRefresh() {
Toast.makeText(this.context, "Refresh", Toast.LENGTH_SHORT).show()
val rootView = this_inflater!!.inflate(R.layout.fragment_pending_job, this_container, false)
val url = "something"
val request_queue = Volley.newRequestQueue(this.context)
val stringRequest = StringRequest(Request.Method.GET, url,
Response.Listener<String> { response ->
val pending_job = Gson().fromJson<ArrayList<HashMap<String, String>>>(
response.toString(),
object : TypeToken<ArrayList<HashMap<String, String>>>() {}.type
)
val adapter: CustomAdapter = CustomAdapter(
this.context,
pending_job
)
val listView = rootView.findViewById(R.id.pending_job_list) as ListView
listView.adapter = adapter
pending_job_list_layout.isRefreshing = false
pending_job_list_layout.setOnRefreshListener(this)
request_queue.stop()
}, Response.ErrorListener { error ->
if (error.networkResponse != null) {
Toast.makeText(this.context, error.networkResponse.statusCode.toString(), Toast.LENGTH_SHORT).show()
}
request_queue.stop()
})
request_queue.add(stringRequest)
}
That is just instantiating the CustomAdapter again and take it to the listview again.
When I refresh, nothing in the list is changed.
Define a method UpdateData in CustomAdapter class which will receive new data and replaces old data
fun UpdateData(new_job_list: ArrayList<HashMap<String, String>>){
job_list.clear()
job_list.addAll(new_job_list)
notifyDataSetChanged()
}
Now when you get new data from volly, instead of setting adapter again for listview just use
adapter.UpdateData(pending_job)
1.Way:
You can make background listener with Asynctask .You can listen to your server in a loop continuous.When data changed than you can refresh the listview.This way is easy but not good for performance.
2.Way:
You can use firebase database.Because it is working realtime.
3.Way:
If you don't want change your database.You can use Google Cloud Messaging.You must implement this, both server and your application.When data changed on server listen and send message with cloud messaging to android app.And refresh list view automatically.

Categories

Resources