I am working on a project and stuck on this for 3 days now. The project reads a json, using an adapter, and fragments.
json: https://opendata.visitflanders.org/accessibility/activities/sport_v2.json
fragment_list_of_sports.xml:
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".ListOfSportsFragment">
<!-- TODO: Update blank fragment layout -->
<TextView
android:id="#+id/textView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="List of sports and locations"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="0.022" />
<androidx.recyclerview.widget.RecyclerView
android:id="#+id/rvSportList"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent"/>
<Button
android:id="#+id/btnBack"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Back"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.049"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="0.976" />
</androidx.constraintlayout.widget.ConstraintLayout>
ListOfSportsFragment.kt:
package vives.be.bedoerikproject
import SportsAdapter
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.core.view.get
import androidx.fragment.app.Fragment
import androidx.recyclerview.widget.LinearLayoutManager
import kotlinx.android.synthetic.main.fragment_list_of_sports.*
import kotlinx.android.synthetic.main.fragment_list_of_sports.view.*
import org.json.JSONException
import org.json.JSONObject
class ListOfSportsFragment : Fragment() {
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
// Inflate the layout for this fragment
val view = inflater.inflate(R.layout.fragment_list_of_sports, container, false)
val sportList: ArrayList<SportModelClass> = ArrayList()
try {
// As we have JSON object, so we are getting the object
//Here we are calling a Method which is returning the JSON object
val obj = JSONObject("Sports.json")
// fetch JSONArray named users by using getJSONArray
val sportsArray = obj.getJSONArray("sports")
// Get the users data using for loop i.e. id, name, email and so on
for (i in 0 until sportsArray.length()) {
// Create a JSONObject for fetching single User's Data
val sport = sportsArray.getJSONObject(i)
// Fetch id store it in variable
val id = sport.getInt("business_product_id")
val name = sport.getString("name")
val city_name = sport.getString("city_name")
val postal_code = sport.getInt("postal_code")
val website = sport.getString("website")
val phone = sport.getString("phone1")
val email = sport.getString("email")
// Now add all the variables to the data model class and the data model class to the array list.
val sportDetails =
SportModelClass(id, name, postal_code, city_name, phone, email, website)
// add the details in the list
sportList.add(sportDetails)
}
} catch (e: JSONException) {
//exception
e.printStackTrace()
}
// Set the LayoutManager that this RecyclerView will use.
rvSportList.layoutManager = LinearLayoutManager(activity) //rv = reciycleview
// Adapter class is initialized and list is passed in the param.
val itemAdapter = SportsAdapter(requireContext(), sportList)
// adapter instance is set to the recyclerview to inflate the items.
rvSportList.adapter = itemAdapter
return view
}
}
SportsAdapter:
import vives.be.bedoerikproject.SportModelClass
import android.content.Context
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView
import vives.be.bedoerikproject.R
import kotlinx.android.synthetic.main.sport_model_layout.view.*
class SportsAdapter(val context: Context, val items: ArrayList<SportModelClass>) :
RecyclerView.Adapter<SportsAdapter.ViewHolder>() {
/**
* Inflates the item views which is designed in xml layout file
*
* create a new
* {#link ViewHolder} and initializes some private fields to be used by RecyclerView.
*/
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
return ViewHolder(
LayoutInflater.from(context).inflate(
R.layout.sport_model_layout,
parent,
false
)
)
}
/**
* Binds each item in the ArrayList to a view
*
* Called when RecyclerView needs a new {#link ViewHolder} of the given type to represent
* an item.
*
* This new ViewHolder should be constructed with a new View that can represent the items
* of the given type. You can either create a new View manually or inflate it from an XML
* layout file.
*/
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
val item = items.get(position)
holder.name.text = item.name
holder.city_name.text = item.city_name
holder.postal_code.text = item.postal_code.toString()
holder.website.text = item.website
holder.phone1.text = item.phone1
holder.email.text = item.email
}
/**
* Gets the number of items in the list
*/
override fun getItemCount(): Int {
return items.size
}
/**
* A ViewHolder describes an item view and metadata about its place within the RecyclerView.
*/
class ViewHolder(view: View) : RecyclerView.ViewHolder(view) {
// Holds the TextView that will add each item to
val name = view.name
val city_name = view.city_name
val postal_code = view.postal_code
val website = view.website
val phone1 = view.phone1
val email = view.email
}
}
SportModelClass.kt:
package vives.be.bedoerikproject
class SportModelClass (
val business_product_id : Int,
val name : String,
val postal_code : Int,
val city_name : String,
val phone1 : String,
val email : String,
val website : String
)
And I get an error in ListOfSportsFragment in line 60, this one:
E/AndroidRuntime: FATAL EXCEPTION: main Process: vives.be.bedoerikproject, PID: 7302 java.lang.NullPointerException: Attempt to invoke virtual method 'androidx.recyclerview.widget.RecyclerView$LayoutManager androidx.recyclerview.widget.RecyclerView.getLayoutManager()' on a null object reference at vives.be.bedoerikproject.ListOfSportsFragment.onCreateView(ListOfSportsFragment.kt:60) at androidx.fragment.app.Fragment.performCreateView(Fragment.java:2995) at androidx.fragment.app.FragmentStateManager.createView(FragmentStateManager.java:523) at androidx.fragment.app.FragmentStateManager.moveToExpectedState(FragmentStateManager.java:261) at androidx.fragment.app.FragmentManager.executeOpsTogether(FragmentManager.java:1840) at androidx.fragment.app.FragmentManager.removeRedundantOperationsAndExecute(FragmentManager.java:1764) at androidx.fragment.app.FragmentManager.execPendingActions(FragmentManager.java:1701) at androidx.fragment.app.FragmentManager$4.run(FragmentManager.java:488) at android.os.Handler.handleCallback(Handler.java:938) at android.os.Handler.dispatchMessage(Handler.java:99) at android.os.Looper.loop(Looper.java:223) at android.app.ActivityThread.main(ActivityThread.java:7656) at java.lang.reflect.Method.invoke(Native Method) at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:592) at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:947) Disconnected from the target VM, address: 'localhost:52495', transport: 'socket'
You can set your layout manager in xml file.
...
<androidx.recyclerview.widget.RecyclerView
android:id="#+id/rvSportList"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent"/>
...
and you don't have to pass context into your adapter, you can reach context in onCreateViewHolder like this
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
return ViewHolder(
LayoutInflater.from(parent.context).inflate(
R.layout.sport_model_layout,
parent,
false
)
)
}
Could you try move your codes from onCreateView to onViewCreated fun on your fragment and use 'context' instead of 'requireContext()'. Hope it hepls.
Use requireActivity() like this:
rvSportList.layoutManager = LinearLayoutManager(requireActivity())
You can use synthetic view binding like this only after your fragment onCreateView() has returned.
Move the view setup code using synthetics such as rvSportList.layoutManager = LinearLayoutManager(activity) //rv = reciycleview to e.g. onViewCreated().
Also worth noting that the kotlin-android-extensions plugin that provides synthetic view binding is deprecated. Consider using view binding instead.
This is your error:
java.lang.NullPointerException: Attempt to invoke virtual method
'androidx.recyclerview.widget.RecyclerView$LayoutManager
androidx.recyclerview.widget.RecyclerView.getLayoutManager()'
on a null object reference at
vives.be.bedoerikproject.ListOfSportsFragment.onCreateView(ListOfSportsFragment.kt:60)
It's trying to call a method (getLayoutManager()) on something that's supposed to be a RecyclerView, but it's not - it's null. Can't call a method on a null object, so you get the NullPointerException
This is line 60, where the log tells you the error is happening:
rvSportList.layoutManager = LinearLayoutManager(activity) //rv = reciycleview
So if the problem is calling methods on a null RecyclerView, and it's happening on that line, then rvSportList isn't a RecyclerView, it's null.
You're getting that magic rvSportList variable from the Kotlin synthetic extensions you're using, which have been deprecated for a long time. You should use View Binding instead - but in the meantime, try this - the basic way to find a view:
val rvSportList = view.findViewById<RecyclerView>(R.id.rvSportList)
rvSportList.layoutManager = LinearLayoutManager(requireContext())
// etc
Related
I am relativley new to Android Studio and I have tried making a RecyclerView for my Application. I have ran into an issue where I cannot update the ViewHolder unless it's inside the ViewHolder itself.
Here is my RecyclerViewAdapter
package com.example.testactivity
import android.graphics.Color
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.LinearLayout
import android.widget.TextView
import android.widget.Toast
import androidx.cardview.widget.CardView
import androidx.recyclerview.widget.RecyclerView
class RecyclerViewAdapter() : RecyclerView.Adapter<RecyclerViewAdapter.ViewHolder>() {
inner class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
var name : TextView
var description : TextView
var listCard : CardView
init {
name = itemView.findViewById(R.id.projname)
description = itemView.findViewById(R.id.desc)
listCard = itemView.findViewById(R.id.listCard)
}
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
val v = LayoutInflater.from(parent.context).inflate(R.layout.recyclerview, parent, false)
return ViewHolder(v)
}
override fun onBindViewHolder(holder: RecyclerViewAdapter.ViewHolder, position: Int) {
holder.name.text = ideaList[position].name
holder.description.text = ideaList[position].description
holder.listCard.setCardBackgroundColor(Color.parseColor(ideaList[position].color))
holder.itemView.findViewById<LinearLayout>(R.id.linLayout).setOnClickListener(View.OnClickListener {
ideaList.remove(ideaList[position])
println(position.toString())
notifyItemChanged(ideaList.size)
})
}
override fun getItemCount(): Int {
return ideaList.size
}
fun addItem(name: String, desc: String, color: String) {
val idea = Idea(
name,
desc,
color
)
ideaList.add(idea)
notifyItemInserted(ideaList.size-1)
println((ideaList.size-1).toString())
}
}
And this is my MainActivity
class MainActivity : AppCompatActivity() {
//private var layoutManager: LayoutManager? = null
//private var adapter: RecyclerView.Adapter<RecyclerViewAdapter.ViewHolder>? = null
override fun onCreate(savedInstanceState: Bundle?) {
lateinit var layoutManager : LayoutManager
lateinit var adapter : RecyclerViewAdapter
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
//Switch Activity
val accbtn = findViewById<ImageButton>(R.id.accInf)
accbtn.setOnClickListener {
val i = Intent(this, SecondActivity::class.java)
//i.putExtra("", value) **IMPORT THE ACCOUNT INFORMATION ONTO THIS PAGE
this.startActivity(i)
this.overridePendingTransition(0, 0);
}
// Add Item
val addbtn = findViewById<ImageButton>(R.id.addBtn)
val bottomSheet = BottomSheetFragment()
// Use the add button to open a modal to add an item to a grid
addbtn.setOnClickListener {
//Open bottom sheet modal to add item
bottomSheet.show(supportFragmentManager, "BottomSheetDialog")
}
// Add Item to Recycler view
val recycleView = findViewById<RecyclerView>(R.id.recyclerview)
layoutManager = LinearLayoutManager(this)
adapter = RecyclerViewAdapter()
recycleView.layoutManager = layoutManager
recycleView.adapter = adapter
}
I have tried using it in the MainActivity and inside of the ViewAdapter, aswell when I did "notifyItemChanged(ideaList.size)" inside of the ViewHolder it worked, but it doesn't work with any of the notify functions outside of this.
Not only does notifyOnItemChanged work anywhere, but the onBindViewHolder is the one place you should probably never call it- the point of that function is to bind a view, preferably without side effects. Luckily you aren't doing that, you're doing it in a callback set in that function which is totally different.
THe reason your call doesn't work is the call is wrong. Your code is:
ideaList.remove(ideaList[position])
println(position.toString())
notifyItemChanged(ideaList.size)
Let's say the size of the list is 10, and the position is 4 at the beginning of this function. First off, you didn't change anything- you removed something. Changed would be if you swapped the value at position X with a different value, but kept the size and placement unchanged. So it should be notifyItemRemoved, not notifyItemChanged. Secondly, the index you send is wrong- you'd be sending 9 in my example above. You should be sending 4, the index of the item removed. So you want notifyItemRemoved(position)
MainActivity is as follows.
Nothing comes out on the screen, but I don't know which part is wrong.
How can I add the value of TV_item_name?
And in the log, it's from the DetailViewAdapter of the inner class.
Log.d (logTag,onCreateViewHolder11iscaled") value is also not output, so what is the problem?
package com.example.test_recyclerview
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.util.Log
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.TextView
import androidx.cardview.widget.CardView
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.example.test_recyclerview.databinding.ActivityMainBinding
class MainActivity : AppCompatActivity() {
private lateinit var binding : ActivityMainBinding
var logTag : String? = "로그 MainActivity"
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater)
val view = binding.root
setContentView(view)
Log.d(logTag,"111 onCreate is called")
binding.mRecyclerView.layoutManager = LinearLayoutManager(this)
val adapter = DetailViewAdapter()
Log.d(logTag,"222 onCreate is called")
binding.mRecyclerView.adapter = adapter
}
override fun onDestroy() {
super.onDestroy()
binding.mRecyclerView.adapter = null
}
inner class DetailViewAdapter : RecyclerView.Adapter<DetailViewAdapter.ViewHolder>() {
private var list = ArrayList<String>()
var logTag : String? = "로그 MainActivity"
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): DetailViewAdapter.ViewHolder {
Log.d(logTag,"onCreateViewHolder11 is called")
list = getItemList()
val view = LayoutInflater.from(parent.context).inflate(R.layout.item_custom_row, parent, false)
return ViewHolder(view)
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
Log.d(logTag,"onBindViewHolder is called")
for (i in 1 until list.size) {
Log.d(logTag,"onBindViewHolder is called // list[$i] =" + list[i])
holder.tvItem.text = list[i]
}
}
override fun getItemCount(): Int {
return list.size
}
private fun getItemList(): ArrayList<String> {
for (i in 1..8) {
list.add(i, "Item $i")
}
return list
}
inner class ViewHolder(view: View) : RecyclerView.ViewHolder(view) {
val tvItem : TextView = view.findViewById(R.id.tv_item_name)
val cardViewItem : CardView = view.findViewById(R.id.card_view_item)
}
}
}
item_custom_row.xml
<?xml version="1.0" encoding="utf-8"?>
`enter code here`<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<androidx.cardview.widget.CardView
android:id="#+id/card_view_item"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="5dp"
android:padding="10dp"
app:cardCornerRadius="5dp"
app:cardElevation="3dp">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:orientation="horizontal"
tools:ignore="UseCompoundDrawables">
<ImageView
android:layout_width="50dp"
android:layout_height="50dp"
android:contentDescription="#string/app_name"
android:src="#mipmap/ic_launcher" />
<TextView
android:id="#+id/tv_item_name"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="10dp"
android:textColor="#android:color/black"
android:textSize="18sp"
android:textStyle="bold"
tools:text="Item" />
</LinearLayout>
</androidx.cardview.widget.CardView>
</LinearLayout>
activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<androidx.recyclerview.widget.RecyclerView
android:id="#+id/m_RecyclerView"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
The issue is you're assigning list in onCreateViewHolder but it won't be called since your list.size is returning 0.
Rightly pointed out in this answer
Create list outside adapter & pass it from constructor
Change implementation in onBindViewHolder
Following is the complete example for your use case:
class MainActivity : AppCompatActivity() {
private lateinit var binding: ActivityMainBinding
var logTag: String? = "로그 MainActivity"
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater)
val view = binding.root
setContentView(view)
Log.d(logTag, "111 onCreate is called")
binding.mRecyclerView.layoutManager = LinearLayoutManager(this)
val adapter = DetailViewAdapter(getItemList())
Log.d(logTag, "222 onCreate is called")
binding.mRecyclerView.adapter = adapter
}
private fun getItemList(): ArrayList<String> {
val list = ArrayList<String>()
for (i in 1..8) {
list.add("Item $i")
}
return list
}
override fun onDestroy() {
super.onDestroy()
binding.mRecyclerView.adapter = null
}
inner class DetailViewAdapter(private val list: ArrayList<String>) :
RecyclerView.Adapter<DetailViewAdapter.ViewHolder>() {
var logTag: String? = "로그 MainActivity"
override fun onCreateViewHolder(
parent: ViewGroup,
viewType: Int
): DetailViewAdapter.ViewHolder {
Log.d(logTag, "onCreateViewHolder11 is called")
val view =
LayoutInflater.from(parent.context).inflate(R.layout.item_custom_row, parent, false)
return ViewHolder(view)
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
Log.d(logTag, "onBindViewHolder is called")
holder.tvItem.text = list[position]
}
override fun getItemCount(): Int {
return list.size
}
inner class ViewHolder(view: View) : RecyclerView.ViewHolder(view) {
val tvItem: TextView = view.findViewById(R.id.tv_item_name)
val cardViewItem: CardView = view.findViewById(R.id.card_view_item)
}
}
}
From the code you posted I can see two problems with your code.
First one is that you are trying to populate the list of data from inside the view holder. What happens is the following:
The adapter is created
The adapter wants to know how many viewholders it should create to see how to populate the recycler view.
The adapter calls getItemCount and this method returns 0, since the list hasen't been populated.
Nothing else is called since nothing else has to be executed.
So, to fix this, the easiest way would be to make getItemCount return 8 and you are set. But, a better way to fix this is to instaintiate your list outside of your adapter, in your activity for example, and pass it as a constructor parameter when you initialize your adapter.
The second problem I'm seeing is on the method onBindViewHolder. You are iterating trough the list to set the text and this will cause that for all items you will only set the text as in the last item (item 8). You need to remember that onBindViewHolder is a method that is called when a view holder needs to refresh it contents because it is going to be used to display a different item of the list, that's why this method is passed the position as parameter, so you can do something like:
holder.tvItem.text = list[position]
** As a side note for the second issue, a more general approach I have seen and used to render the contents of the view holder, is to create a public method called "bind" which is passed the item that needs to be rendered and on the view holder you will have the logic on how to paint it. Something like:
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
holder.bind(list[position])
}
/// Inside the view holder
fun bind(item: String) {
tvItem.text = item
}
I'm trying to set up recycler view to work with the fragment I created, and in MainActivity after "val recyclerV = findViewById(R.id.plantsRecyclerView)" I'm getting " java.lang.RuntimeException: Unable to start activity ComponentInfo{com.example.myapplication/com.example.myapplication.MainActivity}: java.lang.NullPointerException: findViewById<RecyclerVie…(R.id.plantsRecyclerView) must not be null" even though the recycler with that Id exists, any ideas how can I fix that?
Thanks in advance!
(code:
Main Activity:
private var numPlants = 4
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
//there is some parts that I'm not posting because they are not relevant (at least I think so <it's mainly code that creates notifications>)
val recyclerV = findViewById<RecyclerView>(R.id.plantsRecyclerView)
recyclerV.layoutManager = LinearLayoutManager(this)
recyclerV.adapter = MyRecyclerViewAdapter(this, numPlants)
}
}
MyRecyclerViewAdapter:
package com.example.myapplication
import android.content.Context
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView
class MyRecyclerViewAdapter(private val context: Context, private val numPlants: Int) :
RecyclerView.Adapter<MyRecyclerViewAdapter.ViewHolder>() {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
val view = LayoutInflater.from(context).inflate(R.layout.card_layout, parent, false)
return ViewHolder(view)
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
holder.bind(position)
}
override fun getItemCount() = numPlants
inner class ViewHolder(itemView: View): RecyclerView.ViewHolder(itemView) {
fun bind(position: Int){
// not implemented yet
}
}
}
fragment main (the fragment that recycler view inside of it):
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#color/darker_green"
tools:context=".MainActivity">
<androidx.recyclerview.widget.RecyclerView
android:id="#+id/plantsRecyclerView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:layout_marginEnd="8dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
MainFragment (I haven't changed anything here after creation, and I suppose that is the problem):
package com.example.myapplication
import android.os.Bundle
import androidx.fragment.app.Fragment
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
// TODO: Rename parameter arguments, choose names that match
// the fragment initialization parameters, e.g. ARG_ITEM_NUMBER
private const val ARG_PARAM1 = "param1"
private const val ARG_PARAM2 = "param2"
/**
* A simple [Fragment] subclass.
* Use the [MainFragment.newInstance] factory method to
* create an instance of this fragment.
*/
class MainFragment : Fragment() {
// TODO: Rename and change types of parameters
private var param1: String? = null
private var param2: String? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
arguments?.let {
param1 = it.getString(ARG_PARAM1)
param2 = it.getString(ARG_PARAM2)
}
}
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
// Inflate the layout for this fragment
return inflater.inflate(R.layout.fragment_main, container, false)
}
companion object {
/**
* Use this factory method to create a new instance of
* this fragment using the provided parameters.
*
* #param param1 Parameter 1.
* #param param2 Parameter 2.
* #return A new instance of fragment MainFragment.
*/
// TODO: Rename and change types and number of parameters
#JvmStatic
fun newInstance(param1: String, param2: String) =
MainFragment().apply {
arguments = Bundle().apply {
putString(ARG_PARAM1, param1)
putString(ARG_PARAM2, param2)
}
}
}
}
Because your RecyclerView is in MainFragment. Not in MainActivity. Make your RecyclerView in MainFragment.
I am trying to display items on a recyclerview but it says
"No adapter attached; skipping layout"
.
I can't figure out the error. I tried with Activities instead of Fragements and it works super well. I am not yet so familiar with Framents. Kindly help.
RecyclerAdapter
package com.manzugerald.shukuruyesu.adapter
import android.content.Context
import android.content.Intent
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.TextView
import androidx.core.view.isVisible
import androidx.recyclerview.widget.RecyclerView
import com.manzugerald.shukuruyesu.HymnDetailActivity
import com.manzugerald.shukuruyesu.R
import com.manzugerald.shukuruyesu.model.Hymns
class HymnsItemAdapter(
val context: Context,
val dataset: List<Hymns>
) : RecyclerView.Adapter<HymnsItemAdapter.ItemViewHolder>() {
//provide a reference to the views for each data item (in this case there is only one item)
//Complex data items may need more than one view per item, and
//you provide access to all the views for a data item in a view holder
//Each data item is just an Affirmation object
inner class ItemViewHolder(private val view: View) : RecyclerView.ViewHolder(view) {
//val hymn_imageView: ImageView =view.findViewById(R.id.item_image)
val hymn_title: TextView = view.findViewById(R.id.textView_hymn_title)
val hymn_language: TextView = view.findViewById(R.id.textView_hymn_language)
val hymn_number: TextView = view.findViewById(R.id.textView_hymn_number)
var hymn_detail: TextView = view.findViewById(R.id.textView_hymn_text)
//This has been unused. I will try to see how best to use it in the future
var hymnPosition = 0
/**
* //So as the contents of the views displayed are clickable, append the setOnClickListener to the view holder rather than individual views
* An explicit intent is used for exchanging information between two or more fragements or activities (screen)
* The information to be carried to the next screen is passed as a key-value pair
* The key is retrieved in the receiving activity or fragment
* Then the data the key carries is gotten and assigned to the views so as to be viewed
*/
init {
view.setOnClickListener {
val intent = Intent(context,HymnDetailActivity::class.java)
val message = hymn_number.text.toString()
val message_two = hymn_language.text.toString()
val message_three = hymn_title.text.toString()
val message_four = hymn_detail.text.toString()
intent.putExtra("key_one", message)
intent.putExtra("key_two",message_two)
intent.putExtra("key_three",message_three)
intent.putExtra("key_four",message_four)
context.startActivity(intent)
}
}
}
/**
* Create new views (invoked by the layout manager)
*/
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ItemViewHolder {
val adapterLayout = LayoutInflater.from(parent.context).inflate(R.layout.list_item_hymn_list, parent, false)
return ItemViewHolder(adapterLayout)
}
/**
* Replace the contents of a view (invoked by the layout manager)
*/
/**
* Binds or attaches data to the views.
* Does this by interacting with the itemViewHolder
*/
override fun onBindViewHolder(holder: ItemViewHolder, position: Int) {
val item = dataset[position]
//holder.hymn_imageView.setImageResource(item.imageResourceId)
holder.hymn_title.text = context.resources.getString(item.titleStringResourceId)
holder.hymn_language.text = context.resources.getString(item.languageStringResourceId)
holder.hymn_number.text = "Song Number: " + context.resources.getString(item.numberStringResourceId)
holder.hymn_detail.text = context.resources.getString(item.detailStringResourceId)
holder.hymnPosition = position
holder.hymn_detail.isVisible=false
}
/**
* Return the size of the dataset (invoked by the layout manager)
*/
//Takes not of the size of the data, number of rows so that binding can be done with ease
override fun getItemCount(): Int = dataset.size
}
The data source:
package com.manzugerald.shukuruyesu.data
import com.manzugerald.shukuruyesu.R
import com.manzugerald.shukuruyesu.model.Hymns
class HymnsDataSource {
//Gets the data from the string resources
fun loadHymns():List<Hymns>{
return listOf<Hymns>(
Hymns(R.string.affirmation1,R.string.affirmationNum1,R.string.affirmationLang1,R.string.affirmationDetail1),
Hymns(R.string.affirmation2,R.string.affirmationNum2,R.string.affirmationLang2,R.string.affirmationDetail2),
Hymns(R.string.affirmation3,R.string.affirmationNum3,R.string.affirmationLang3,R.string.affirmationDetail3),
Hymns(R.string.affirmation4,R.string.affirmationNum4,R.string.affirmationLang4,R.string.affirmationDetail4),
Hymns(R.string.affirmation5,R.string.affirmationNum5,R.string.affirmationLang5,R.string.affirmationDetail5),
Hymns(R.string.affirmation6,R.string.affirmationNum6,R.string.affirmationLang6,R.string.affirmationDetail6),
Hymns(R.string.affirmation7,R.string.affirmationNum7,R.string.affirmationLang7,R.string.affirmationDetail7),
Hymns(R.string.affirmation8,R.string.affirmationNum8,R.string.affirmationLang8,R.string.affirmationDetail8),
Hymns(R.string.affirmation9,R.string.affirmationNum9,R.string.affirmationLang9,R.string.affirmationDetail9),
Hymns(R.string.affirmation10,R.string.affirmationNum10,R.string.affirmationLang10,R.string.affirmationDetail10)
)
}
}
The Data class:
package com.manzugerald.shukuruyesu.model
import androidx.annotation.DrawableRes
import androidx.annotation.StringRes
data class Hymns(
#StringRes var titleStringResourceId: Int,
#StringRes var numberStringResourceId: Int,
#StringRes var languageStringResourceId: Int,
#StringRes var detailStringResourceId: Int
// #DrawableRes val imageResourceId: Int
)
The Fragment to display the recyclerview:
package com.manzugerald.shukuruyesu
import android.content.Context
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.Fragment
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.manzugerald.shukuruyesu.data.HymnsDataSource
import com.manzugerald.shukuruyesu.databinding.FragmentHymnListBinding
import com.manzugerald.shukuruyesu.adapter.HymnsItemAdapter
import com.manzugerald.shukuruyesu.model.Hymns
// TODO: Rename parameter arguments, choose names that match
// the fragment initialization parameters, e.g. ARG_ITEM_NUMBER
private const val ARG_PARAM1 = "param1"
private const val ARG_PARAM2 = "param2"
/**
* A simple [Fragment] subclass.
* Use the [HymnListFragment.newInstance] factory method to
* create an instance of this fragment.
*/
class HymnListFragment : Fragment() {
// TODO: Rename and change types of parameters
//enable and refrence binding
private var _binding: FragmentHymnListBinding? = null
private val binding get() = _binding!!
//property for the recycler view
private lateinit var recyclerView: RecyclerView
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setHasOptionsMenu(true)
}
//To inflate the layout, call teh onCreatView Method
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
_binding = FragmentHymnListBinding.inflate(inflater,container,false)
val view = binding.root
return view
}
//Bind the views in onViewCreated
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
val myDataSource = HymnsDataSource().loadHymns()
recyclerView = binding.recyclerView
recyclerView.apply {
layoutManager=LinearLayoutManager(requireContext())
adapter= HymnsItemAdapter(dataset = myDataSource, context = requireContext())
adapter= HymnsItemAdapter(context,myDataSource)
}
}
override fun onDestroyView() {
super.onDestroyView()
_binding=null
}
}
HymnListFragment Layout File:
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".HymnListFragment">
<androidx.recyclerview.widget.RecyclerView
android:id="#+id/recycler_View"
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_marginTop="8dp"
android:layout_marginEnd="8dp"
android:layout_marginBottom="8dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:listitem="#layout/list_item_hymn_list"/>
</androidx.constraintlayout.widget.ConstraintLayout>
I got the solution to the question...
Neither the Adapter nor the Fragments had a problem.
The problem was in my MainActivity.kt file, which I didn't upload here...
The error came about when I tried changing from activities to Fragments....
Initially, I forgot to take off or comment the inflation of the layouts (Layouts are inflated in the respective Fragments, not the main activity).
For illustration purposes, I will comment the said error in the code.
My bad!
Main Activity class:
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
//val binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(R.layout.activity_main)
}
}
Getting Data from retrofit, but it will not display to the screen.
I have been learning about live data and retrofit over the last few days and I am doing my best to combine it with recyclerview, but I am messing up somewhere in my MainActivity.kt. I have spent a few hours trying to get it myself, but I have not found the solution for my particular problem. I believe my error either lies in the lateinit portion of the code or my initRecyclerView function.
MainActivity.kt
package com.dev20.retrofitpractice
import android.os.Bundle
import android.util.Log
import androidx.appcompat.app.AppCompatActivity
import androidx.lifecycle.LiveData
import androidx.lifecycle.Observer
import androidx.lifecycle.liveData
import androidx.recyclerview.widget.LinearLayoutManager
import kotlinx.android.synthetic.main.activity_main.*
import retrofit2.Response
class MainActivity : AppCompatActivity() {
private lateinit var albumsAdapter: AlbumsAdapter
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val list = ArrayList<AlbumsItem>()
initRecyclerView()
getData()
albumsAdapter = AlbumsAdapter(list)
}
private fun getData() {
val retService: AlbumService = RetrofitInstance
.getRetrofitInstance()
.create(AlbumService::class.java)
val responseLiveData: LiveData<Response<Albums>> = liveData {
val response = retService.getAlbums()
emit(response)
}
responseLiveData.observe(this, Observer {
val albumsList: MutableListIterator<AlbumsItem>? = it.body()?.listIterator()
if (albumsList != null) {
while (albumsList.hasNext()) {
val albumsItem: AlbumsItem = albumsList.next()
Log.i("MYTAG", albumsItem.title)
}
}
})
}
private fun initRecyclerView() {
recycler_view.apply {
layoutManager = LinearLayoutManager(this#MainActivity)
adapter = albumsAdapter
}
}
}
AlbumsAdapter.kt
package com.dev20.retrofitpractice
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.TextView
import androidx.recyclerview.widget.RecyclerView
import kotlinx.android.synthetic.main.albums_list.view.*
class AlbumsAdapter(private val albumsList: List<AlbumsItem>) : RecyclerView.Adapter<AlbumsAdapter.AlbumsViewHolder>() {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): AlbumsViewHolder {
val itemView = LayoutInflater.from(parent.context).inflate(R.layout.albums_list,
parent, false)
return AlbumsViewHolder(itemView)
}
override fun onBindViewHolder(holder: AlbumsViewHolder, position: Int) {
val currentItem = albumsList[position]
holder.textViewAlbums.text = currentItem.title
}
override fun getItemCount() = albumsList.size
class AlbumsViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
val textViewAlbums: TextView = itemView.text_view_albums
}
}
Albums.kt
package com.dev20.retrofitpractice
class Albums : ArrayList<AlbumsItem>()
AlbumsItem.kt
package com.dev20.retrofitpractice
import com.google.gson.annotations.SerializedName
data class AlbumsItem(
#SerializedName("id")
val id: Int,
#SerializedName("title")
val title: String,
#SerializedName("userId")
val userId: Int
)
AlbumService.kt
package com.dev20.retrofitpractice
import retrofit2.Response
import retrofit2.http.GET
interface AlbumService {
//define abstract function to get albums data
//returns retrofit response of type Albums
//Use suspend modifier because we are using coroutines with retrofit
#GET("/albums")
suspend fun getAlbums() :Response<Albums>
}
activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<androidx.recyclerview.widget.RecyclerView
android:id="#+id/recycler_view"
android:padding="4dp"
android:clipToPadding="false"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:listitem="#layout/albums_list"/>
</androidx.constraintlayout.widget.ConstraintLayout>
ablums_list.xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.cardview.widget.CardView xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginEnd="8dp"
android:layout_marginStart="8dp"
android:layout_marginTop="8dp">
<RelativeLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:padding="8dp">
<TextView
android:id="#+id/text_view_albums"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
</RelativeLayout>
</androidx.cardview.widget.CardView>
MainActivity oncreate add :
albumsAdapter = AlbumAdapter(albumList)
Your adapter is not initialized. Try initializing it. And please note that passing the list via constructor is not the best approach to do it. Suppose if you have a list that is to be updated, will you initialize the adapter every time for it to be done? No! So what you should do is create a separate method for setting the list to Adapter.
UPDATE
You are setting an empty list to the adapter and then after that you pass the list to the adapter. So arrange your code like this:-
initRecyclerView()
getData()
albumsAdapter = AlbumsAdapter(list)