onCreateViewHolder() of child recycler view not being called in Android [Kotlin] - android

I have a nested recycler view structure. The data for the recycler view is coming from Room Database and it follows MVVM architecture with repositories and viewmodels.
I am able to inflate the parent recycler view and get the list on screen but the child recycler view is not being inflated. I can see the value being passed through the adapter by using logs. But that is not being used on onBindViewHolder() and neither onCreateViewHolder() is called
Any help would be appreciated.
My CODE:
Fragment :
class ControlPanelFragment : Fragment() {
private var _binding: FragmentControlPanelBinding? = null
private val binding: FragmentControlPanelBinding get() = _binding!!
private lateinit var controlPanelViewModel: ControlPanelViewModel
//floor adapter
private lateinit var adapter: FloorsAdapter
//rooms adapter
private lateinit var roomAdapter: RoomsAdapter
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
_binding = FragmentControlPanelBinding.inflate(layoutInflater)
controlPanelViewModel = ViewModelProvider(this)[ControlPanelViewModel::class.java]
adapter = FloorsAdapter()
roomAdapter = RoomsAdapter()
return binding.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
binding.rvFloors.adapter = adapter
binding.rvFloors.layoutManager = LinearLayoutManager(requireContext())
ListItemControlPanelFloorsBinding.inflate(layoutInflater).apply {
rvRoomControlPanel.adapter = roomAdapter
rvRoomControlPanel.layoutManager = LinearLayoutManager(activity)
Timber.d("Inside list item")
}
controlPanelViewModel.getAllFloors.observe(viewLifecycleOwner, Observer {
Timber.d("List is $it")
//Remove duplicates from received list
val distinct = it.toSet().toList().sorted()
Timber.d("List after removing duplicates and sorting: $distinct")
adapter.floorList(distinct)
for (i in distinct) {
controlPanelViewModel.getAllRooms(i).observe(viewLifecycleOwner, Observer { rooms ->
Timber.d("Floor: $i, Rooms: $rooms")
val distinctRooms = rooms.toSet().toList()
roomAdapter.roomList(distinctRooms)
})
}
})
}
override fun onDestroyView() {
super.onDestroyView()
_binding = null
}
}
Parent recycler view adapter
open class FloorsAdapter() : RecyclerView.Adapter<FloorsAdapter.FloorViewHolder>() {
private var floorList = emptyList<String>()
inner class FloorViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {}
override fun onCreateViewHolder(
parent: ViewGroup,
viewType: Int
): FloorViewHolder {
val layoutInflater = LayoutInflater.from(parent.context)
val binding = ListItemControlPanelFloorsBinding.inflate(layoutInflater, parent, false)
return FloorViewHolder(binding.root)
}
#SuppressLint("NotifyDataSetChanged")
override fun onBindViewHolder(holder: FloorsAdapter.FloorViewHolder, position: Int) {
val item = floorList[position]
Timber.d("Current floor is $item, Floor List is : $floorList")
ListItemControlPanelFloorsBinding.bind(holder.itemView).apply {
Timber.d("Current floor is $item, Floor List is : $floorList")
tvFloor.text = "Floor : $item"
}
}
#SuppressLint("NotifyDataSetChanged")
fun floorList(floors: List<String>) {
this.floorList = floors
notifyDataSetChanged()
}
override fun getItemCount(): Int {
return floorList.size
}
}
Child Recycler view adapter:
class RoomsAdapter() : RecyclerView.Adapter<RoomsAdapter.RoomsViewHolder>() {
private var roomList = emptyList<String>()
inner class RoomsViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView)
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RoomsViewHolder {
val inflater = LayoutInflater.from(parent.context)
val binding = ListItemControlPanelRoomsBinding.inflate(inflater, parent, false)
return RoomsViewHolder(binding.root)
}
override fun onBindViewHolder(holder: RoomsViewHolder, position: Int) {
Timber.d("Room List onBindViewHolder: $roomList")
val item = roomList[position]
ListItemControlPanelRoomsBinding.bind(holder.itemView).apply {
tvRoom.text = item
}
}
override fun getItemCount(): Int {
return roomList.size
}
#SuppressLint("NotifyDataSetChanged")
fun roomList(room: List<String>) {
this.roomList = room
Timber.d("Room List: $roomList")
notifyDataSetChanged()
}
}
Fragment layout.xml
<?xml version="1.0" encoding="utf-8"?>
<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="match_parent"
android:orientation="vertical"
tools:context=".ui.controlPanel.ui.ControlPanelFragment">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:elevation="10dp">
<com.google.android.material.switchmaterial.SwitchMaterial
android:id="#+id/switchFactory"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginTop="60dp"
android:checked="false"
android:text="Factory"
android:textOff="OFF"
android:textOn="ON"
android:textSize="16sp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<Spinner
android:id="#+id/spnChooseFloor"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="200dp"
android:layout_marginEnd="16dp"
app:layout_constraintBottom_toBottomOf="#+id/switchFactory"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="#+id/switchFactory"
app:layout_constraintTop_toTopOf="#+id/switchFactory" />
</androidx.constraintlayout.widget.ConstraintLayout>
<androidx.cardview.widget.CardView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginStart="16dp"
android:layout_marginTop="10dp"
android:layout_marginEnd="16dp"
android:layout_marginBottom="10dp"
android:elevation="8dp"
app:cardCornerRadius="8dp">
<androidx.recyclerview.widget.RecyclerView
android:id="#+id/rvFloors"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:listitem="#layout/list_item_control_panel_floors" />
</androidx.cardview.widget.CardView>
</LinearLayout>
List Item for fragment recycler view(parent recycler view)
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="200dp"
android:orientation="vertical">
<TextView
android:id="#+id/tvFloor"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="4dp"
android:text="Floor 1"
android:textColor="#color/black"
android:textSize="20sp" />
<androidx.recyclerview.widget.RecyclerView
android:id="#+id/rvRoomControlPanel"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="10dp"
tools:listitem="#layout/list_item_control_panel_rooms" />
</LinearLayout>
List item for child recycler view
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="40dp">
<TextView
android:id="#+id/tvRoom"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:padding="8dp"
android:text="Conference Room"
android:textSize="20sp" />
</LinearLayout>
Edit 1:
My entity :
data class AddedDevicesInformation(
#ColumnInfo(name = "floor_name")
var floorName: String = "",
#ColumnInfo(name = "room_name")
var roomName: String = "",
#ColumnInfo(name = "machine_name")
var machineName: String = "",
#ColumnInfo(name = "device_name")
var deviceName: String = "",
#ColumnInfo(name = "factory_status")
var factoryStatus: Boolean? = false,
#PrimaryKey(autoGenerate = true)
var id: Int? = null,
) {
}
DAO query :
//returns all rooms associated with a floor
#Query("SELECT room_name from added_device_information where floor_name =:floor")
fun readAllRoomsOnAFloor(floor: String): LiveData<List<String>>
Repository:
class ControlPanelRepository(private val devicesInformationDao: DevicesInformationDao) {
fun getAllRooms(floor: String): LiveData<List<String>> = devicesInformationDao.readAllRoomsOnAFloor(floor)
ViewModel:
class ControlPanelViewModel(application: Application) : AndroidViewModel(application) {
//repository instance
val repository: ControlPanelRepository
//Variable for getting all floors
val getAllFloors: LiveData<List<String>>
init {
val database = PowerManagementDatabase.getDatabase(application)
val dao = database.getAddedDevicesInformationDao()
repository = ControlPanelRepository(dao)
getAllFloors = repository.getAllFloors
}
fun getAllRooms(floor: String): LiveData<List<String>> {
return repository.getAllRooms(floor)
}
}

The problem is that each "floor" (row in the floor RecyclerView) has its own unique "room" RecyclerView, but you never attach an adapter to those room RecyclerViews. Instead, you inflate an unused "floor" layout in the Fragment and attach a room adapter to it. That layout will never be displayed anywhere so its adapter is never used.
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
binding.rvFloors.adapter = adapter
binding.rvFloors.layoutManager = LinearLayoutManager(requireContext())
// The problem is this part.
// This view isn't shown anywhere, and the views inflated in FloorsAdapter
// never get a RoomAdapter attached
ListItemControlPanelFloorsBinding.inflate(layoutInflater).apply {
rvRoomControlPanel.adapter = roomAdapter
rvRoomControlPanel.layoutManager = LinearLayoutManager(activity)
Timber.d("Inside list item")
}
Each displayed floor layout is inflated in the floor adapter in onCreateViewHolder and the data for the floor then gets set in onBindViewHolder. This means you need to create a separate RoomsAdapter for each floor and attach it inside the floor adapter somewhere.
This makes accessing the room adapters more difficult with your current pattern, but if you make a Floor object that contains both its name and its list of rooms, then the floor object selected in onBindViewHolder can pass the list of rooms to its RoomAdapter. This means the FloorsAdapter would take a List<Floor> instead of List<String>. Then in the main fragment you only need to pass the list of Floor objects to the floor adapter and it will handle passing room lists on to the room adapters appropriately.
data class Floor(val name: String, val rooms: List<String>) {}
The floor adapter would look something like this:
// modify the ViewHolder to store the binding (so you don't have to
// re-bind the views so often) and store the room adapter for this
// floor. There should be one room adapter per floor.
inner class FloorViewHolder(val binding: ListItemControlPanelFloorsBinding,
val roomAdapter: RoomsAdapter)
: RecyclerView.ViewHolder(binding.root) {}
override fun onCreateViewHolder(
parent: ViewGroup,
viewType: Int
): FloorViewHolder {
val layoutInflater = LayoutInflater.from(parent.context)
val binding = ListItemControlPanelFloorsBinding.inflate(layoutInflater, parent, false)
// Create one adapter per floor here
val adapter = RoomsAdapter()
binding.rvRoomControlPanel.adapter = adapter
binding.rvRoomControlPanel.layoutManager = LinearLayoutManager(activity)
return FloorViewHolder(binding, adapter)
}
override fun onBindViewHolder(holder: FloorsAdapter.FloorViewHolder, position: Int) {
// for a given floor, set the rooms on that floor's room adapter
val currentFloor = floorList[position]
holder.roomAdapter.roomList(currentFloor.rooms)
holder.binding.tvFloor.text = "Floor : ${currentFloor.name}"
}
Here's an example of how that might look in the ViewModel, when it constructs the list of Floor objects, and the activity would observe the floorList LiveData. Each Floor contains all the data (including nested data) that it needs to display.
private val floorListLiveData = MutableLiveData<List<Floor>>()
val floorList: LiveData<List<Floor>>
get() = floorListLiveData
fun load() {
viewModelScope.launch {
val floors = mutableListOf<Floor>()
val floorList = dao.readFloors()
for(floorName in floorList) {
val rooms = dao.readRoomsInFloor(floorName)
floors.add(Floor(floorName, rooms))
}
floorListLiveData.postValue(floors)
}
}

Related

Kotlin-Attempt to invoke virtual method 'void androidx.recyclerview.widget.RecyclerView.setAdapter(androidx.recyclerview.widget.RecyclerView$Adapter)'

I Am trying to run the application but it crashes when i try to access the content of a bottom navigation bar which has a fragment in it and the fragement cointains a recyclerView.The adpater is null here is the error
java.lang.NullPointerException: Attempt to invoke virtual method 'void androidx.recyclerview.widget.RecyclerView.setAdapter(androidx.recyclerview.widget.RecyclerView$Adapter)' on a null object reference
at com.example.accers.ChatFragment.recyclerView(ChatFragment.kt:67)
at com.example.accers.ChatFragment.onCreateView(ChatFragment.kt:41)
Fragment xml
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout 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=".ChatFragment">
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<LinearLayout
android:id="#+id/ll_layout_bar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:background="#E4E4E4"
android:orientation="horizontal">
<EditText
android:id="#+id/et_message"
android:inputType="textShortMessage"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="10dp"
android:layout_weight=".5"
android:background="#drawable/round_button"
android:backgroundTint="#android:color/white"
android:hint="Type a message..."
android:padding="10dp"
android:singleLine="true" />
<Button
android:id="#+id/btn_send"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_margin="10dp"
android:layout_weight="1"
android:background="#drawable/round_button"
android:backgroundTint="#26A69A"
android:text="Send"
android:textColor="#android:color/white" />
</LinearLayout>
<androidx.recyclerview.widget.RecyclerView
android:id="#+id/rv_messages"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_above="#id/ll_layout_bar"
tools:itemCount="20"
tools:listitem="#layout/message_item" />
<!-- <View-->
android:layout_below="#+id/dark_divider"
<!-- android:layout_width="match_parent"-->
<!-- android:layout_height="10dp"-->
<!-- android:background="#42A5F5"-->
<!-- android:id="#+id/dark_divider"/>-->
</RelativeLayout>
</FrameLayout>
Fragment Class
class ChatFragment : Fragment() {
private val TAG = "ChatFragment"
var messagesList = mutableListOf<Message>()
private lateinit var adapter: MessagingAdapter
private val botList = listOf("Cassandra", "Francesca", "Luigi", "Nico","Lesley","Hiyle","Roselind")
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?): View? {
// Inflate the layout for this fragment
var view = inflater.inflate(R.layout.fragment_chat, container, false)
// var button: Button = view.findViewById(R.id.btn_send)
recyclerView()
clickEvents()
val random = (0..3).random()
customBotMessage("Hello! Today you're speaking with ${botList[random]}, how may I help?")
return view
}
private fun clickEvents() {
//Send a message
btn_send.setOnClickListener {
sendMessage()
}
// Scroll back to correct position when user clicks on text view
et_message.setOnClickListener {
GlobalScope.launch {
delay(100)
withContext(Dispatchers.Main) {
rv_messages.scrollToPosition(adapter.itemCount - 1)
}
}
}
}
private fun recyclerView() {
adapter = MessagingAdapter()
rv_messages.adapter = adapter
rv_messages.layoutManager = LinearLayoutManager(activity)
}
override fun onStart() {
super.onStart()
//In case there are messages, scroll to bottom when re-opening app
GlobalScope.launch {
delay(100)
withContext(Dispatchers.Main) {
rv_messages.scrollToPosition(adapter.itemCount - 1)
}
}
}
private fun sendMessage() {
val message = et_message.text.toString()
val timeStamp = Time.timeStamp()
if (message.isNotEmpty()) {
//Adds it to our local list
messagesList.add(Message(message, SEND_ID, timeStamp))
et_message.setText("")
adapter.insertMessage(Message(message, SEND_ID, timeStamp))
rv_messages.scrollToPosition(adapter.itemCount - 1)
botResponse(message)
}
}
private fun botResponse(message: String) {
val timeStamp = Time.timeStamp()
GlobalScope.launch {
//Fake response delay
delay(1000)
withContext(Dispatchers.Main) {
//Gets the response
val response = BotResponse.basicResponses(message)
//Adds it to our local list
messagesList.add(Message(response, RECEIVE_ID, timeStamp))
//Inserts our message into the adapter
adapter.insertMessage(Message(response, RECEIVE_ID, timeStamp))
//Scrolls us to the position of the latest message
rv_messages.scrollToPosition(adapter.itemCount - 1)
//Starts Google
when (response) {
OPEN_GOOGLE -> {
val site = Intent(Intent.ACTION_VIEW)
site.data = Uri.parse("https://www.google.com/")
startActivity(site)
}
OPEN_SEARCH -> {
val site = Intent(Intent.ACTION_VIEW)
val searchTerm: String? = message.substringAfterLast("search")
site.data = Uri.parse("https://www.google.com/search?&q=$searchTerm")
startActivity(site)
}
}
}
}
}
private fun customBotMessage(message: String) {
GlobalScope.launch {
delay(1000)
withContext(Dispatchers.Main) {
val timeStamp = Time.timeStamp()
messagesList.add(Message(message, RECEIVE_ID, timeStamp))
adapter.insertMessage(Message(message, RECEIVE_ID, timeStamp))
rv_messages.scrollToPosition(adapter.itemCount - 1)
}
}
}
}
My Adapter class
class MessagingAdapter: RecyclerView.Adapter<MessagingAdapter.MessageViewHolder>() {
var messagesList = mutableListOf<Message>()
inner class MessageViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
init {
itemView.setOnClickListener {
//Remove message on the item clicked
messagesList.removeAt(adapterPosition)
notifyItemRemoved(adapterPosition)
}
}
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MessageViewHolder {
return MessageViewHolder(
LayoutInflater.from(parent.context).inflate(R.layout.message_item, parent, false)
)
}
override fun getItemCount(): Int {
return messagesList.size
}
#SuppressLint("SetTextI18n")
override fun onBindViewHolder(holder: MessageViewHolder, position: Int) {
val currentMessage = messagesList[position]
when (currentMessage.id) {
SEND_ID -> {
holder.itemView.tv_message.apply {
text = currentMessage.message
visibility = View.VISIBLE
}
holder.itemView.tv_bot_message.visibility = View.GONE
}
RECEIVE_ID -> {
holder.itemView.tv_bot_message.apply {
text = currentMessage.message
visibility = View.VISIBLE
}
holder.itemView.tv_message.visibility = View.GONE
}
}
}
fun insertMessage(message: Message) {
this.messagesList.add(message)
notifyItemInserted(messagesList.size)
}
}
MainActivity Class
Main Activity class has the bottom nav bar to replace the fragments
val navHostFragment = supportFragmentManager.findFragmentById(R.id.mainContainer) as NavHostFragment
navController = navHostFragment.navController
val bottomNavigationView = findViewById<BottomNavigationView>(R.id.bottomNavigationView)
setupWithNavController(bottomNavigationView, navController)
I Have tried simillar solutions but i can't still figure how to apply simillar asked question and errors. Thank you
You are using kotlin synthetics. Internally it will work as getView().findViewById(R.id.rv_messages)
Since in oncreateView, you are trying to access view before even view is attached in the fragment layout tree.
getView() will always return null.
Several things you can do. You can pass view to the recycler view function and access like view.rv_messages.
It's better to handle like the below.
Else you can move all view related to onViewCreated(). In onCreateView() you will just inflate and return the view. So in onViewCreated() when it calls getView() , since view is already added in onCreateView it will return the correct view object.
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?): View? {
// Inflate the layout for this fragment
var view = inflater.inflate(R.layout.fragment_chat, container, false)
return view
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
recyclerView()
clickEvents()
val random = (0..3).random()
customBotMessage("Hello! Today you're speaking with ${botList[random]}, how may I help?")
}
Also synthetics have been deprecated, and currently, it is not recommended. will strongly recommend you to use view binding for binding the views. As ,earlier I had a weird issue with synthetics which I have covered here.
Refer here for more details about deprecation of kotlin synthetics.

Handle multiple recycler views in Android [Kotlin]

I have a situation where I have 3 RecyclerView on a single Layout. They are dependent on each other in a certain way. Data is coming from Room Database.
Question Prototype (Problem statement): Let's say you have floors like (Floor1, Floor2, Floor3 etc.) and inside each floor you have rooms like (Room1, Room2, Room3 etc.) and inside each room you have People with name like (PersonA, PersonB, PersonC).
Given constraint is that : A person cannot be in two different rooms at same type.
Edit 1: Floors, Rooms and Persons are coming from the database in the form of a list of strings.
How would you show that using maybe ( a recycler view ) or anything on a single screen layout.
There can be infinite number of floors, rooms and persons. But that information is fetch from a room database.
My approach : (This is not a complete approach), But I am thinking like having one RecyclerView at the top which holds number of floors. We use a query to get the total floors from the database and sort them and display. During the onBindViewHolder() in the adapter for floors we have a condition which checks if there is a room associated with that floor and if there is then we make another query to fetch from database and show it (maybe using a new recycler View) but I don't know how to do that. Is nested recycler view a thing that can be used here.
It doesn't stop here as we make another query to database using that room name to find all the persons inside that room. Which is another recycler view.
I am thinking of applying this approach but I feel there are many stoppage for implementing this. And I am not sure if this is the way to handle such things.
Image for reference :
I am looking for any information if you have been through any such situations then what approach did you followed. Is there any library that can be used to simplify this task or anything that you can provide knowledge of would be helpful. Thanks!
Edit 2: What's working now :
I tried implementing the nested recycler view. Things seems to be working fine as now I have two adapters currently (for floor and rooms) and I am inflating the floor adapter from the fragment and inflating the rooms adapter from onBindViewHolder of FloorsAdapter.
The problem I am facing now that inside recycler view I am getting the list of floors correctly but for the list of rooms(in child recycler view) I am only getting it for last floor.
Check this image for reference (Floor 2 and 1 also have rooms but I am only getting room C1 which is present in last floor which is 3) :
Current code in Adapter :
class FloorsAdapter(
private val controlPanelViewModel: ControlPanelViewModel,
private val activity: FragmentActivity?
) : RecyclerView.Adapter<FloorsAdapter.FloorViewHolder>() {
private var floorList = emptyList<String>()
private lateinit var roomAdapter: RoomAdapter
inner class FloorViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {}
override fun onCreateViewHolder(
parent: ViewGroup,
viewType: Int
): FloorViewHolder {
val layoutInflater = LayoutInflater.from(parent.context)
val binding = ListItemControlPanelFloorsBinding.inflate(layoutInflater, parent, false)
return FloorViewHolder(binding.root)
}
override fun onBindViewHolder(holder: FloorsAdapter.FloorViewHolder, position: Int) {
val item = floorList[position]
ListItemControlPanelFloorsBinding.bind(holder.itemView).apply {
Timber.d("Current floor is $item, Floor List is : $floorList")
tvFloor.text = item
roomAdapter = RoomAdapter(controlPanelViewModel, activity)
rvRoomControlPanel.adapter = roomAdapter
rvRoomControlPanel.layoutManager = LinearLayoutManager(activity)
controlPanelViewModel.getAllRooms(item).observeForever(Observer {
Timber.d("List of rooms : $it")
//Finding distinct rooms
val distinct = it.toSet().toList()
Timber.d("Distinct rooms list : $distinct")
roomAdapter.roomList(distinct)
})
}
}
#SuppressLint("NotifyDataSetChanged")
fun floorList(floors: List<String>) {
this.floorList = floors
notifyDataSetChanged()
}
override fun getItemCount(): Int {
return floorList.size
}
}
class RoomAdapter(
private val controlPanelViewModel: ControlPanelViewModel,
private val activity: FragmentActivity?
) : RecyclerView.Adapter<RoomAdapter.RoomViewHolder>() {
private var roomList = emptyList<String>()
inner class RoomViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RoomViewHolder {
val layoutInflater = LayoutInflater.from(parent.context)
val binding = ListItemControlPanelRoomsBinding.inflate(layoutInflater, parent, false)
return RoomViewHolder(binding.root)
}
override fun onBindViewHolder(holder: RoomViewHolder, position: Int) {
val item = roomList[position]
ListItemControlPanelRoomsBinding.bind(holder.itemView).apply {
Timber.d("Current room is $item")
tvRoom.text = item
}
}
#SuppressLint("NotifyDataSetChanged")
fun roomList(room: List<String>) {
this.roomList = room
notifyDataSetChanged()
}
override fun getItemCount(): Int {
return roomList.size
}
}
Code in Fragment for inflating the Floors Adapter:
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
binding.rvFloors.adapter = adapter
binding.rvFloors.layoutManager = LinearLayoutManager(requireContext())
controlPanelViewModel.getAllFloors.observe(viewLifecycleOwner, Observer{
Timber.d("List is $it")
//Remove duplicates from received list
val distinct = it.toSet().toList()
Timber.d("List after removing duplicates: $distinct")
adapter.floorList(distinct)
})
Timber.d("Adapter: $adapter" )
}
Edit 3: Going through the logs I found something which explains why is it happening. The list of floors is getting executed first because of this
controlPanelViewModel.getAllFloors.observe(viewLifecycleOwner, Observer{
Timber.d("List is $it")
//Remove duplicates from received list
val distinct = it.toSet().toList()
Timber.d("List after removing duplicates: $distinct")
adapter.floorList(distinct)
})
As we are inflating the rooms recycler view from floors adapter here
roomAdapter = RoomAdapter(controlPanelViewModel, activity)
rvRoomControlPanel.adapter = roomAdapter
rvRoomControlPanel.layoutManager = LinearLayoutManager(activity)
controlPanelViewModel.getAllRooms(item).observeForever(Observer {
Timber.d("List of rooms : $it")
//Finding distinct rooms
val distinct = it.toSet().toList()
Timber.d("Distinct rooms list : $distinct")
roomAdapter.roomList(distinct)
})
It is going to be little delayed as data is being fetched from Room. So instead of doing something like (Floor 1 -> Room1, Room2 we are getting something like (Floor1, Floor2 -> Room3, Room4). The data about rooms for previous floors is getting lost.
What to do here? How to stop the execution of next floors unless we have fetched all the rooms and shown using the textview.
I have tried replicate the exact scenario with one vertical recycler view and horizontal recycler view. Here is the thing, one vertical recyclerview for Floors. Then one in floor item, list of room item views can be added to vertical linear layout container. A single room item view contains horizontal recyclerview for person.
Floor Vertical RV -> Adding Custom Room Items to Vertical LL -> Person
Horizontal RV
Hope it helps.
Activity:
class MainActivity : AppCompatActivity() {
private lateinit var recyclerView: RecyclerView
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
recyclerView = findViewById(R.id.mainRv)
recyclerView.apply {
adapter = FloorAdapter(getFloors(), this#MainActivity)
layoutManager = LinearLayoutManager(this#MainActivity)
setHasFixedSize(true)
addItemDecoration(DividerItemDecoration(context, DividerItemDecoration.VERTICAL))
}
}
fun getFloors(): List<Floor> {
val list = ArrayList<Floor>()
for (i in 0 until 6) {
list.add(Floor())
}
return list
}
}
<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/mainRv"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
Adapters:
class FloorAdapter(val floors: List<Floor>, val context:Context) : RecyclerView.Adapter<FloorViewHolder>() {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): FloorViewHolder {
return FloorViewHolder(LayoutInflater.from(context).inflate(R.layout.floor_item,null))
}
override fun onBindViewHolder(holder: FloorViewHolder, position: Int) {
holder.bindView(floors[position])
}
override fun getItemCount(): Int {
return floors.size
}
}
class PersonAdapter(val persons: List<Person>, val context:Context) : RecyclerView.Adapter<PersonViewHolder>() {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): PersonViewHolder {
return PersonViewHolder(LayoutInflater.from(context).inflate(R.layout.person_item,null))
}
override fun onBindViewHolder(holder: PersonViewHolder, position: Int) {
holder.bindView(persons[position])
}
override fun getItemCount(): Int {
return persons.size
}
}
View Holders:
class PersonViewHolder(val view:View) : RecyclerView.ViewHolder(view) {
fun bindView(person: Person) {
view.findViewById<TextView>(R.id.person_tv).text = "Person:${person.personId}"
}
}
class FloorViewHolder(val view:View) : RecyclerView.ViewHolder(view){
fun bindView(floor: Floor){
val str = "Floor : ${floor.floorId}"
view.findViewById<TextView>(R.id.floor).text = str
view.findViewById<LinearLayout>(R.id.room_container).let { ll->
ll.removeAllViews()
floor.rooms.forEach{
val myView= LayoutInflater.from(view.context).inflate(R.layout.room_item,null)
myView.findViewById<TextView>(R.id.room).text = "RoomId :${it.roomId}"
myView.findViewById<RecyclerView>(R.id.persons_rv)?.apply {
adapter = PersonAdapter(it.persons,view.context)
layoutManager = LinearLayoutManager(view.context,LinearLayoutManager.HORIZONTAL,false)
setHasFixedSize(true)
}
ll.addView(myView)
}
}
}
}
Data Class:
data class Floor(val floorId: Int = (0..900).random()) {
var rooms = arrayListOf<Room>()
init {
for (i in 0 until 50) {
rooms.add(Room())
}
}
}
data class Room(val roomId: Int = (0..900).random()) {
var persons = arrayListOf<Person>()
init {
for (i in 0 until 200) {
persons.add(Person())
}
}
}
data class Person(val personId: Int = (0..900).random())
floor_item.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"
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:id="#+id/floor"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="22sp"
android:textStyle="bold"
android:textColor="#android:color/holo_red_dark"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<LinearLayout
android:id="#+id/room_container"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="10dp"
android:orientation="vertical"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="#+id/floor"
/>
</androidx.constraintlayout.widget.ConstraintLayout>
person_item.xml
<?xml version="1.0" encoding="utf-8"?>
<com.google.android.material.textview.MaterialTextView xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="wrap_content"
android:padding="18dp"
android:id="#+id/person_tv"
android:textSize="18sp"
android:layout_margin="10dp"
android:background="#android:color/holo_blue_light"
android:textColor="#color/black"
android:layout_height="wrap_content">
</com.google.android.material.textview.MaterialTextView>
room_item.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"
android:layout_width="match_parent"
android:padding="20dp"
android:layout_height="match_parent">
<TextView
android:id="#+id/room"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textColor="#android:color/holo_green_dark"
android:textSize="18sp"
android:textStyle="bold"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<androidx.recyclerview.widget.RecyclerView
android:id="#+id/persons_rv"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="10dp"
app:layout_constraintTop_toBottomOf="#id/room" />
</androidx.constraintlayout.widget.ConstraintLayout>

Swipe down update from Firebase in RecyclerView

I am trying to make update of my RecyclerViewer with data from Storage for profile picture and Firestore for other data. But I tried many methods and I don't know how to make this.
The goal is to make a RecyclerViewer update each time user goes to fragment and also be able to swipe to refresh the list. Also the layout gets inflated after second 'visit' on the Fragment, the first time it does not get inflated. Is this a problem with a Glide?
And the cache keeps the data in RecyclerViewer even when new user is logged in with completely different data in Firebase.
fragment_item_list.xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.swiperefreshlayout.widget.SwipeRefreshLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/swiperefresh"
android:layout_width="match_parent"
android:layout_height="match_parent">
<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:id="#+id/itemContainer"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".FriendFragment">
<androidx.recyclerview.widget.RecyclerView
android:id="#+id/list"
android:name="com.msmmhm.hauimetyourmother.FriendFragment"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginLeft="16dp"
android:layout_marginRight="16dp"
app:layoutManager="LinearLayoutManager"
tools:context=".FriendFragment"
tools:listitem="#layout/fragment_item" />
</androidx.constraintlayout.widget.ConstraintLayout>
</androidx.swiperefreshlayout.widget.SwipeRefreshLayout>
FriendFragment.kt
imports ...
class FriendFragment : Fragment() {
private lateinit var binding: FragmentItemListBinding
private lateinit var mSwipeRefreshLayout: SwipeRefreshLayout
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
binding = FragmentItemListBinding.inflate(inflater, container, false)
with(binding.list) {
layoutManager = LinearLayoutManager(context)
adapter = MyFriendRecyclerViewAdapter(Friend.ITEMS,container!!.context)
return binding.root
}
}
}
MyFriendRecyclerViewAdapter.kt
imports ...
class MyFriendRecyclerViewAdapter(
private val values: List<FriendItem>,
val context: Context
) : RecyclerView.Adapter<MyFriendRecyclerViewAdapter.ViewHolder>() {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
return ViewHolder(
FragmentItemBinding.inflate(
LayoutInflater.from(parent.context),
parent,
false
)
)
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
val item = values[position]
var events: CollectionReference = FirebaseFirestore.getInstance().collection("events")
val docRef = events.document(item.id)
Log.i(ContentValues.TAG,"EVENTS ITEM ID: "+item.id)
docRef.get()
.addOnSuccessListener { document ->
if (document.exists())
{
Log.i(ContentValues.TAG,"DOCUMENT EXISTS")
val map = document.data
if (map!!.size != 0)
{
holder.activeIcon.setColorFilter(R.drawable.presence_online)
}
else
{
holder.activeIcon.setImageResource(R.drawable.presence_invisible)
}
}
else
{
holder.activeIcon.setImageResource(R.drawable.presence_invisible)
}
}
holder.friendNickname.text = item.friendName
loadWithGlide2(item.id,holder)
}
fun loadWithGlide2(id: String, holder: ViewHolder) {
val storageReference = Firebase.storage.reference.child("ProfilePictures/${id}.jpg")
Log.d(ContentValues.TAG, "NAME IS: ${storageReference.name}")
Glide.with(context)
.load(storageReference)
.error(R.drawable.defaultprofilepicture)
.into(holder.friendImg)
}
override fun getItemCount(): Int = values.size
inner class ViewHolder(binding: FragmentItemBinding) : RecyclerView.ViewHolder(binding.root) {
val friendImg: ImageView = binding.friendPicture
var friendNickname: TextView = binding.friendNickname
var activeIcon: ImageView = binding.activeIcon
val itemContainer: View = binding.root
override fun toString(): String {
return super.toString() + " '" + friendNickname.text + "'"
}
}
}

I'm trying to print a list on the screen using the recyclerview, but the code in the adapter part is not working

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
}

kotlin recyclerview data not showing

Our question is how to show Parent and Child data from two SQLite DB tables?
We have two tables that are added to two ArrayLists childList and parentList.
Here are the Model Class's for each
class ModelParent {
var idD:Int = 0
var dept:String = ""
var fkD:Int = 0
var children: List<ModelChild> = mutableListOf()
//var children: ArrayList<ModelChild>? = null
//var children: List<ModelChild> by Delegates.notNull()
constructor (children: List<ModelParent>) : this()
companion object {
var globalVar = 1
}
}
class ModelChild {
var idI:Int = 0
var item:String = ""
var fkI:Int = 0
}
We have two Adapters for each table and will post that code
We are able to iterate through the two ArrayList with this code and display the data in the format we would like to show in the ViewActivity.
fun theGET(){
val db = DBHelper(this)
childList = db.queryITEM()
parentList = db.queryDEPT()
var PL = parentList.size
do {
var DEPT: String = parentList[z].dept
var PARENT_LIST_FK = parentList.get(z).fkD
println("========== Dept " + DEPT + " fkD " + PARENT_LIST_FK)
val FK = PARENT_LIST_FK
childList = db.queryALL(FK)
var CL = childList.size
for (a in 0..CL - 1) {
var CHILD_ITEM = childList[a].item
var CHILD_LIST_FK = childList[a].fkI
println("========== item " + CHILD_ITEM+" fkI "+CHILD_LIST_FK)
}
z++
}
while (z <= PL-1)
}
We will post the View Activity
class ViewActivity : AppCompatActivity() {
lateinit var recyclerView: RecyclerView
private var parentList:List<ModelParent> = ArrayList()
private var childList:List<ModelChild> = ArrayList()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_view)
initRecycler()
}// end onCreate
private fun initRecycler() {
val db = DBHelper(this)
childList = db.queryITEM()
parentList = db.queryDEPT()
recyclerView = rv_parent
recyclerView.apply{
layoutManager = LinearLayoutManager(this#ViewActivity, LinearLayout.VERTICAL, false)
adapter = ViewAdapter(parentList)
adapter = ViewChildAdapter(children = childList)
}
}
}
ViewActivity has this XML file
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.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=".ViewActivity">
<android.support.v7.widget.RecyclerView
android:id="#+id/rv_parent"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</android.support.constraint.ConstraintLayout>
The Two Adapters and coresponding XML files
class ViewAdapter(private val parents:List<ModelParent>):RecyclerView.Adapter<ViewAdapter.ViewHolder>() {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
val view = LayoutInflater.from(parent.context).inflate(R.layout.the_view,parent,false)
return ViewHolder(view)
}
override fun getItemCount(): Int {
return parents.size
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
val parent = parents[position]
holder.textView.text = parent.dept
holder.recyclerView.apply {
layoutManager = LinearLayoutManager(holder.recyclerView.context, LinearLayout.VERTICAL, false) as RecyclerView.LayoutManager?
adapter = ViewChildAdapter(parent.children!!)
}
}
inner class ViewHolder(itemView : View) : RecyclerView.ViewHolder(itemView){
val recyclerView : RecyclerView = itemView.rv_child
val textView: TextView = itemView.textView
}
}
class ViewChildAdapter(private val children:List<ModelChild>):RecyclerView.Adapter<ViewChildAdapter.ViewHolder>() {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
val view = LayoutInflater.from(parent.context).inflate(R.layout.child_recycler,parent,false)
return ViewHolder(view)
}
override fun getItemCount(): Int {
return children.size
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
val child = children[position]
holder.textView.text = child.item
}
inner class ViewHolder(itemView : View) : RecyclerView.ViewHolder(itemView){
val textView : TextView = itemView.child_textView
}
}
Inflated XML files
<android.support.v7.widget.CardView
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:card_view="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="#+id/card_view"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_margin="2dp"
card_view:cardBackgroundColor="#fff"
card_view:cardCornerRadius="5dp"
card_view:cardElevation="4dp"
card_view:cardUseCompatPadding="true">
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<TextView
android:id="#+id/textView"
style="#style/Base.TextAppearance.AppCompat.Subhead"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignStart="#+id/rv_child"
android:layout_alignParentTop="true"
android:padding="20dp"
android:background="#color/color_super_lightGray"
android:text="Dept Header"
android:textColor="#color/color_Purple"
android:textSize="24sp"
android:textStyle="bold" />
<android.support.v7.widget.RecyclerView
android:id="#+id/rv_child"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentStart="true"
android:layout_alignParentTop="true"
android:layout_alignParentBottom="true"
android:layout_marginTop="70dp"
android:layout_marginBottom="0dp"
android:orientation="horizontal"
android:paddingLeft="4dp"
android:paddingTop="8dp"
tools:layout_editor_absoluteX="74dp" />
</RelativeLayout>
</android.support.v7.widget.CardView>
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:orientation="horizontal"
android:layout_width="wrap_content"
android:layout_height="wrap_content">
<TextView
android:id="#+id/child_textView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentStart="true"
android:layout_alignParentTop="true"
android:layout_marginStart="32dp"
android:layout_marginEnd="8dp"
android:layout_marginBottom="8dp"
android:background="#color/color_Transparent"
android:padding="10dp"
android:text="TextView"
android:textColor="#color/color_Black"
android:textSize="20sp"
android:textStyle="bold"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.0"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="0.0" />
When the ViewActivity is loaded ONLY the childList is displayed.
We have tried various changes and can not display the parent List though using theGET fun the parentList data is displayed so we know it is in the list.
We can run the fun theGET and crate a new ArrayList that seems futile.
Our concern is that the parentList is displayed and then removed when the childList is displayed.
We do not know how to prove this.
So our question is how to show Parent and Child data in a organized fashion in the View Activity?
We are adding New CODE based on #Cruces answer
Some issues with this code are beyond out understanding
1. We have no way to run the fun join to create the newList
2. Parent and Child ViewHolder can be called only with receiver of containing Class
3. Too many inner Class's and do we need an Outer Nested annotation?
4. or if both parent and child implement an interface a List< IItem > )
We do not know how to write an interface and connect it to the JoinAdapter
While the answer poses new question we feel it better to ask with in this context = Context HUMOR
Here is the FIX for the JoinAdapter
class JoinAdapter(internal var context: Context, val parents: List<ModelParent>) : RecyclerView.Adapter<JoinAdapter.MyViewHolder>() {
val items = mutableListOf<Any>()
init {
parents //parents should be passed as a constructor argument
.forEach {
items.add(it)
items.addAll(it.children)
}
}
override fun getItemCount(): Int = items.size;
fun getItem(position: Int): Any = items[position]
override fun getItemViewType(position: Int): Int = if (getItem(position) is ModelParent) 0 else 1
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MyViewHolder {
var view: View? = null
if (viewType == 0) {
view = LayoutInflater.from(parent.context).inflate(R.layout.the_view, parent, false)
return ParentViewHolder(view!!)
} else {
view = LayoutInflater.from(parent.context).inflate(R.layout.child_recycler, parent, false)
return ChildViewHolder(view!!)
}
}
override fun onBindViewHolder(holder: MyViewHolder, position: Int) = holder.bindData(position, getItem(position))
inner abstract class MyViewHolder(view: View) : RecyclerView.ViewHolder(view) {
abstract fun bindData(position: Int, item: Any)
}
inner class ParentViewHolder(view: View) : MyViewHolder(view) {
override fun bindData(position: Int, item: Any) {
val parent = item as? ModelParent ?: return
parent.dept
parent.fkD
parent.children
//bind the data here
}
init {
val textView: TextView = view.textView
var editCLICK: RelativeLayout = view.findViewById(R.id.editCLICK) as RelativeLayout
//do the view setup here
}
}
inner class ChildViewHolder(view: View) : MyViewHolder(view) {
init {
val textView : TextView = itemView.child_textView
//do the view setup here
}
override fun bindData(position: Int, item: Any) {
val child = item as? ModelChild ?: return
child.item
child.idI
//bind the data here
}
}
I am going to post the View Activity call to Join Adapter
Code does not FAIL it just shows nothing?
RecyclerAdapter1 = JoinAdapter(parents = ArrayList(),context = applicationContext)
(recyclerView as RecyclerView).adapter = RecyclerAdapter1
Here is the ViewJoinActivity it will load the Parent Data NO Child Data
class ViewJoinActivity : AppCompatActivity() {
lateinit var recyclerView: RecyclerView
private var RecyclerAdapter: JoinAdapter? = null
private var linearLayoutManager: LinearLayoutManager? = null
private val db = DBHelper(this)
private var parentList:List<ModelParent> = ArrayList()
private var childList:List<ModelChild> = ArrayList()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_view_join)
initRecycler()
}// end onCreate
override fun onResume() {
super.onResume()
initDB()
}
private fun initDB() {
parentList = db.queryDEPT()
//childList = db.queryCHILD(1)
childList = db.queryITEM()
// queryCHILD only selects records with a fkI equal to idD
// SEE THE ModelChild and ModelParent
if(parentList.isEmpty()){
title = "No Records in DB"
}else{
title = "Parent List"
}
RecyclerAdapter = JoinAdapter(parents = parentList, context = applicationContext)
(recyclerView as RecyclerView).adapter = RecyclerAdapter
}
private fun initRecycler() {
val db = DBHelper(this)
childList = db.queryITEM()
parentList = db.queryDEPT()
//recyclerView = rv_parent
/*var PL = parentList.size
newList.clear()
do {
var DEPT: String = parentList[z].dept
var ND:String = DEPT
var PARENT_LIST_FK = parentList.get(z).fkD
var PL_ST = ND+" "+PARENT_LIST_FK
newList.add(PL_ST)
println("========== Dept " + DEPT + " fkD " + PARENT_LIST_FK)
val FK = PARENT_LIST_FK
childList = db.queryCHILD(FK)
var CL = childList.size
for (a in 0..CL - 1) {
var CHILD_ITEM = childList[a].item
var NI:String = childList[a].item
var CHILD_LIST_FK = childList[a].fkI
var IL_ST = NI+" "+CHILD_LIST_FK
newList.add(IL_ST)
println("========== item " + CHILD_ITEM+" fkI "+CHILD_LIST_FK)
}
z++
g++
}
while (z <= PL-1)
var ui = newList.size
g=0
for(g in 0..ui-1){
var N2 = newList[g]
if(N2.toString().contains("1")){
println("********************** We Found "+N2)
}
println("############### BOTH = "+N2)
}*/
recyclerView = this.findViewById(R.id.rv_parent)
RecyclerAdapter = JoinAdapter(parents = parentList, context = applicationContext)
linearLayoutManager = LinearLayoutManager(applicationContext)
(recyclerView as RecyclerView).layoutManager = linearLayoutManager!!
//recyclerView.apply {
//layoutManager = LinearLayoutManager(this#ViewJoinActivity, LinearLayout.VERTICAL, false)
//adapter = JoinAdapter(children = childList)
//}
}
}
Calling the Join Adapter from a Activity is the issue? ?
This Activity has a XML associated file with a RecyclerView rv_parent
The way I would do it is flatten the list edit: like this in the constructor
val items : MutableList<Any> = mutableListOf<Any>()
init() {
parents //parents should be passed as a constructor argument
.asSequence() //this is only needed if you want to also order them
.sortedBy { it.idD } //again only if you want to sort them
.forEach {
items.add(it)
items.addAll(it.children)
}
}
I would create a List < Any > (or if both parent and child implement an interface a List< IItem > ) that would contain all the data as you wish to see it, so for example it could look like this:
val items : List<Any> = listOf(parent1, child11,child12,child13,parent2,child12,child13,child14.....etc)
then I would implement a single adapter with multiple view types like this:
override fun getItemCount(): Int {
return items.size
}
fun getItem(position: Int) : Any { //or the interface
return items[position]
}
override getItemViewType (position: Int) : Int {
if (getItem(position) is ModelParent)
return 0
return 1
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
var view : View? = null
if (viewType == 0) {
view = LayoutInflater.from(parent.context).inflate(R.layout.parent_layout,parent,false)
return ParentViewHolder(view);
} else {
view = LayoutInflater.from(parent.context).inflate(R.layout.child_layout,parent,false)
return ChildViewHolder(view);
}
}
and then I would use two view holders to represent how the data is shown for the specific item
inner class ParentViewHolder(itemView : View) : RecyclerView.ViewHolder(itemView){ ...}
inner class ChildViewHolder(itemView : View) : RecyclerView.ViewHolder(itemView){ ...}
after that I could perform different bindings by getting the type of the view ,something like this:
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
if (getItemType(position) == 0) {
(holder as ParentViewHolder).bindData(....)
} else {
(holder as ChildViewHolder).bindData(....)
}
}
edit: Here is the complete adapter I built, it is based on two layout files:
list_item_child:
<android.support.v7.widget.CardView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:card_view="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="#+id/card_view"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_margin="2dp"
card_view:cardBackgroundColor="#fff"
card_view:cardCornerRadius="5dp"
card_view:cardElevation="4dp"
card_view:cardUseCompatPadding="true">
<TextView
android:id="#+id/child_item"
style="#style/Base.TextAppearance.AppCompat.Display3"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentTop="true"
android:padding="20dp"
android:textSize="24sp"
android:textStyle="bold"
tools:text="Dept Header" />
</android.support.v7.widget.CardView>
list_item_parent:
<android.support.v7.widget.CardView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:card_view="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="#+id/card_view"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_margin="2dp"
card_view:cardBackgroundColor="#fff"
card_view:cardCornerRadius="5dp"
card_view:cardElevation="4dp"
card_view:cardUseCompatPadding="true">
<TextView
android:id="#+id/parent_department"
style="#style/Base.TextAppearance.AppCompat.Headline"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentTop="true"
android:padding="20dp"
tools:text="Dept Header"
android:textSize="24sp"
android:textStyle="bold" />
</android.support.v7.widget.CardView>
and the adapter would look something like this:
import android.content.Context
import android.support.v7.widget.RecyclerView
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.TextView
data class ModelParent(val id: Int, val children: List<ModelChild>)
data class ModelChild(val id: Int)
class JoinAdapter(internal var context: Context, val parents: List<ModelParent>) : RecyclerView.Adapter<JoinAdapter.MyViewHolder>() {
val items = mutableListOf<Any>()
init {
parents //parents should be passed as a constructor argument
.forEach {
items.add(it)
items.addAll(it.children)
}
}
override fun getItemCount(): Int = items.size;
fun getItem(position: Int): Any = items[position]
override fun getItemViewType(position: Int): Int = if (getItem(position) is ModelParent) 0 else 1
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MyViewHolder {
var view: View? = null
if (viewType == 0) {
view = LayoutInflater.from(parent.context).inflate(R.layout.rv_list_item_parent, parent, false)
return ParentViewHolder(view!!)
} else {
view = LayoutInflater.from(parent.context).inflate(R.layout.rv_list_item_child, parent, false)
return ChildViewHolder(view!!)
}
}
override fun onBindViewHolder(holder: MyViewHolder, position: Int) = holder.bindData(position, getItem(position))
inner abstract class MyViewHolder(view: View) : RecyclerView.ViewHolder(view) {
abstract fun bindData(position: Int, item: Any)
}
inner class ParentViewHolder(view: View) : MyViewHolder(view) {
var parentDept: TextView = view.findViewById(R.id.parent_department) as TextView
override fun bindData(position: Int, item: Any) {
val parent = item as? ModelParent ?: return
parentDept.text = parent.dept
}
}
inner class ChildViewHolder(view: View) : MyViewHolder(view) {
var childItem: TextView = view.findViewById(R.id.child_item) as TextView
override fun bindData(position: Int, item: Any) {
val child = item as? ModelChild ?: return
childItem.text = child.item
}
}
}
this when used on a single recyclerview will display all children under their parent in a list
Here is a answer and some observations about the design of this app
The answer does NOT place both the parent and child data on the same Recycler View List But the answer may help to solve the issue With further exploration!
What we did was create the parent list and when the Parent Dept is clicked on the corresponding Child Items will be displayed. This may be a more functional design while shopping you only need to look at Produce items while in that section. This means less scrolling through a single master list of Parents and Children.
A word or two about the design we are guessing a grocery store might have 20 or more Departments (Parents) so how you plan to know which Items (Child data) is connected to the appropriate Dept (Parent) needs drastic redesign when building these two lists.
If you downloaded the code from GitHub you will have a better grasp about this design deficiency
Here is all the code with the XML files
Main Activity navigates to View Activity
fun onViewAll(view: View){
val intent = Intent(this,ViewActivity::class.java)
intent.putExtra("pickADAPTER",2)
startActivity(intent)
}
Here is the View Activity
class ViewActivity : AppCompatActivity() {
lateinit var recyclerView: RecyclerView
private var RecyclerAdapter1: ViewAdapter? = null
private var RecyclerAdapter2: ViewChildAdapter? = null
private var linearLayoutManager: LinearLayoutManager? = null
private val db = DBHelper(this)
private var parentList:List<ModelParent> = ArrayList()
private var childList:List<ModelChild> = ArrayList()
var idD = 0
var whichADAPTER = 0
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_view)
val bundle: Bundle = intent.extras
idD = bundle.getInt("BIGi", 0)
whichADAPTER = bundle.getInt("pickADAPTER",0)
initRecycler()
}// end onCreate
override fun onResume() {
super.onResume()
initDB()
}
private fun initDB() {
parentList = db.queryDEPT()
childList = db.queryCHILD(idD)
// queryCHILD only selects records with a fkI equal to idD
// SEE THE ModelChild and ModelParent
if(parentList.isEmpty()){
title = "No Records in DB"
}else{
title = "Parent List"
}
if(whichADAPTER == 2) {
RecyclerAdapter1 = ViewAdapter(parents = parentList, context = applicationContext)
(recyclerView as RecyclerView).adapter = RecyclerAdapter1
}else{
RecyclerAdapter2 = ViewChildAdapter(children = childList)
(recyclerView as RecyclerView).adapter = RecyclerAdapter2
}
}
private fun initRecycler() {
val db = DBHelper(this)
childList = db.queryITEM()
parentList = db.queryDEPT()
recyclerView = rv_parent
recyclerView = this.findViewById(R.id.rv_parent)
RecyclerAdapter1 = ViewAdapter(parents = parentList, context = applicationContext)
linearLayoutManager = LinearLayoutManager(applicationContext)
(recyclerView as RecyclerView).layoutManager = linearLayoutManager!!
}
Here the XML file for View Activity
<android.support.v7.widget.RecyclerView
android:id="#+id/rv_parent"
android:layout_width="match_parent"
android:layout_height="match_parent" />
Here is the View Adapter
class ViewAdapter(private val parents: List<ModelParent>, internal var context: Context):RecyclerView.Adapter<ViewAdapter.ViewHolder>() {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
val view = LayoutInflater.from(parent.context).inflate(R.layout.the_view,parent,false)
return ViewHolder(view)
}
override fun getItemCount(): Int {
return parents.size
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
val parent = parents[position]
holder.textView.text = parent.dept
holder.editCLICK.setOnClickListener {
val i = Intent(context, ViewActivity::class.java)
i.putExtra("BIGi",parent.idD)
i.flags = Intent.FLAG_ACTIVITY_NEW_TASK
context.startActivity(i)
}
}
inner class ViewHolder(itemView : View) : RecyclerView.ViewHolder(itemView){
//val recyclerView : RecyclerView = itemView.rv_child
val textView: TextView = itemView.textView
var editCLICK: RelativeLayout = itemView.findViewById(R.id.editCLICK) as
RelativeLayout
And the inflated XML
<android.support.v7.widget.CardView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:card_view="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="#+id/card_view"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_margin="2dp"
card_view:cardBackgroundColor="#FF0000"
card_view:cardCornerRadius="5dp"
card_view:cardElevation="4dp"
card_view:cardUseCompatPadding="true">
<RelativeLayout
android:id="#+id/editCLICK"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<TextView
android:id="#+id/textView"
style="#style/Base.TextAppearance.AppCompat.Subhead"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignStart="#+id/rv_child"
android:layout_alignParentTop="true"
android:padding="10dp"
android:background="#color/color_lightGray"
android:text="Dept Header"
android:textColor="#color/color_Purple"
android:textSize="24sp"
android:textStyle="bold" />
<android.support.v7.widget.RecyclerView
android:id="#+id/rv_child"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentStart="true"
android:layout_alignParentTop="true"
android:layout_alignParentBottom="true"
android:layout_marginTop="50dp"
android:layout_marginBottom="0dp"
android:orientation="horizontal"
android:paddingLeft="4dp"
android:paddingTop="6dp"
tools:layout_editor_absoluteX="74dp" />
</RelativeLayout>
This is the search routine in DBHelper
fun queryCHILD(fkI: Int): List<ModelChild> {
val db = this.writableDatabase
val childList = ArrayList<ModelChild>()
val selectQuery = "SELECT * FROM $CHILD_TABLE WHERE $colCFK = ?"
val cursor = db.rawQuery(selectQuery, arrayOf(fkI.toString()))
if (cursor != null) {
if (cursor.moveToFirst()) {
do {
val contact = ModelChild()
contact.idI = Integer.parseInt(cursor.getString(cursor.getColumnIndex(colidI)))
contact.item = cursor.getString(cursor.getColumnIndex(colItem))
contact.fkI = Integer.parseInt(cursor.getString(cursor.getColumnIndex(colCFK)))
childList.add(contact)
} while (cursor.moveToNext())
}
}
cursor.close()
return childList
}
One thing to make note of we attached the OnClickListener to a Relative Layout
holder.editCLICK.setOnClickListener
Why we are not sure you can obtain parent.idD type information from a RecyclerView set as a listener?
We need to explore this but in our testing the code failed when we tried.
In the ViewActivity class, you have first set the recyclerView adapter to ViewAdapter and then to ViewChildAdapter thus, now, the adapter of recyclerView is ViewChildAdapter instead of ViewAdapter. Remove this line and the problem will be resolved.

Categories

Resources