Previously I have asked about calling API in fragments and now that I have my data returning in my app I would like to know how to make RecyclerView in this fragments?
Basically this is what i'm looking for to achieve at the end
Code
HomeFragment.kt
class HomeFragment : Fragment() {
private lateinit var homeViewModel: HomeViewModel
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
homeViewModel = ViewModelProviders.of(this).get(HomeViewModel::class.java)
val root = inflater.inflate(R.layout.fragment_home, container, false)
val textView: TextView = root.findViewById(R.id.text_home)
//calling the API
callAPIDemo(textView)
// homeViewModel.text.observe(this, Observer {
// textView.text = it
// })
return root
}
fun callAPIDemo(textView: TextView) {
// Instantiate the RequestQueue.
val queue = Volley.newRequestQueue(activity)
val url = "https://example.com/api/listings"
// Request a string response from the provided URL.
val stringRequest = StringRequest(
Request.Method.GET, url,
Response.Listener<String> { response ->
// Display the first 500 characters of the response string.
textView.text = "Response is: ${response.substring(0, 500)}"
},
Response.ErrorListener { textView.text = "Something is wrong!" })
// Add the request to the RequestQueue.
queue.add(stringRequest)
}
}
fragment_home.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/text_home"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:layout_marginTop="8dp"
android:layout_marginEnd="8dp"
android:textAlignment="center"
android:textSize="20sp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
This is all i have at the moment.
Please note that I'm newbie in this, giving help with little bit of detailed explanation is much appreciated.
Update
Here is what I've created so far
HomeFragment.kt
class HomeFragment : Fragment() {
private lateinit var homeViewModel: HomeViewModel
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
homeViewModel = ViewModelProviders.of(this).get(HomeViewModel::class.java)
val root = inflater.inflate(R.layout.fragment_home, container, false)
val textView: TextView = root.findViewById(R.id.recycler)
//calling the API
//callAPIDemo(textView)
val adapter = RecyclerAdapter(callAPIDemo(textView))
recycler.adapter = adapter
return root
}
fun callAPIDemo(textView: TextView) {
// Instantiate the RequestQueue.
val queue = Volley.newRequestQueue(activity)
val url = "https://example.com/api/listings"
// Request a string response from the provided URL.
val stringRequest = StringRequest(
Request.Method.GET, url,
Response.Listener<String> { response ->
// Display the response.
textView.text = "Response is: ${response.substring(0)}"
},
Response.ErrorListener { textView.text = "Something is wrong!" })
// Add the request to the RequestQueue.
queue.add(stringRequest)
}
}
ListingAdapter.kt
This class is red and returning error of
Parameters must have type annotation
class RecyclerAdapter(List) :
RecyclerView.Adapter<RecyclerAdapter.RecyclerViewHolder>() {
//Binding data for each tile
override fun onBindViewHolder(holder: RecyclerViewHolder, position: Int) {
}
//Creation of view holder for each tile
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerViewHolder {
val inflater = LayoutInflater.from(parent.context)
context = parent.context //this context is red
return RecyclerViewHolder(inflater, parent)
}
//View Holder for each item in recycler
inner class RecyclerViewHolder(inflater: LayoutInflater, parent: ViewGroup) :
RecyclerView.ViewHolder(inflater.inflate(R.layout.listings_layout, parent, false)) {
}
override fun getItemCount(): Int {
TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
}
}
listings_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"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<androidx.cardview.widget.CardView
android:layout_margin="3dp"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<RelativeLayout
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<ImageView
android:id="#+id/imageViewImage"
android:layout_width="match_parent"
android:layout_height="195dp"
android:background="#color/colorPrimary"
android:contentDescription="#string/Image"
android:scaleType="matrix" />
<TextView
android:id="#+id/textViewTitle"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="#string/MakeUp"
android:textAppearance="#style/TextAppearance.AppCompat.Large" />
<TextView
android:id="#+id/textViewSlug"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="#string/MakeUp"
android:textAppearance="#style/TextAppearance.AppCompat.Large" />
</RelativeLayout>
</androidx.cardview.widget.CardView>
</LinearLayout>
And finally my fragment_home.xml
<androidx.recyclerview.widget.RecyclerView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginStart="8dp"
android:layout_marginTop="8dp"
android:layout_marginEnd="8dp"
android:textAlignment="center"
android:textSize="20sp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
android:id="#+id/recycler" />
First You will need to create a layout for how a single row in your list will look(considering we are creating horizontal listing)
navigate to res > layout folder in your project, in layout folder create a Layout Resource File (fancy name, but it's just a XML file)
for example:-
layout_demo_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="wrap_content"
android:layout_margin="8dp">
<ImageView
android:id="#+id/a_image"
android:layout_width="80dp"
android:layout_height="80dp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="#+id/text_title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="20dp"
android:layout_marginEnd="20dp"
android:textSize="18sp"
app:layout_constraintBottom_toBottomOf="#+id/a_image"
app:layout_constraintStart_toEndOf="#+id/a_image"
app:layout_constraintTop_toTopOf="#+id/a_image" />
<TextView
android:id="#+id/text_random"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="20dp"
android:layout_marginEnd="20dp"
android:textSize="18sp"
app:layout_constraintBottom_toBottomOf="#+id/text_title"
app:layout_constraintStart_toEndOf="#+id/text_title"
app:layout_constraintTop_toTopOf="#+id/text_title" />
</androidx.constraintlayout.widget.ConstraintLayout>
Second you need an Adapter.
An Adapter will take a list/array of data from you, render number of rows based on size of list and assign the data to each row based on position on list
for example:-
You have an array of 6 names, then the adapter will create 6 identical row based on layout_demo_item.xml and assign data like 0th position data in array will be assigned to first element of list
MyAdapter.kt
class MyAdapter(var dataList: MutableList<DataModelObject>) : RecyclerView.Adapter<MyAdapter.MyViewHolder>() {
// inflating a layout in simple word is way of using a xml layout inside a class.
// if you look this is the same thing that your fragment doing inside onCreateView and storing that into "root" variable, we just did that in "view"
// we passed "view" to MyViewHolder and returned an object of MyViewHolder class(this object actually a single row without data)
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MyViewHolder {
val view = LayoutInflater.from(parent.context).inflate(R.layout.layout_demo_item, parent, false)
return MyViewHolder(view)
}
//the returned object of MyViewHolder class will come here and also with the position on which it gonna show,
// now based on this position we can get the value from our list and finally set it to the corresponding view that we have here in variable "holder"
override fun onBindViewHolder(holder: MyViewHolder, position: Int) {
holder.textView.text = dataList.get(position).title
holder.textViewRandom.text = dataList.get(position).randomText
//hardcoding the image, just for simplicity, you can set this also from data list same as above
holder.aImage.setImageResource(R.mipmap.ic_launcher)
//here is the image setup by using glide
//Glide.with(holder.aImage.context).load(dataList.get(position).image).into(holder.aImage)
}
// this method telling adapter to how many total rows will be rendered based on count, so we returning the size of list that we passing
override fun getItemCount(): Int {
return dataList.size
}
// while creating the object in onCreateViewHolder, we received "view" here, and as you can see in your fragment,
// we can do find views by id on it same as we doing in fragment
// so your MyViewHolder now hold the reference of the actual views, on which you will set data
class MyViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
var textView: TextView
var textViewRandom: TextView
var aImage: ImageView
init {
textViewRandom = itemView.findViewById(R.id.text_random)
textView = itemView.findViewById(R.id.text_title)
aImage = itemView.findViewById(R.id.a_image)
}
}
}
here is the data class that we using as our model class(same as a POJO class in Java but it's just in Kotlin)
data class DataModelObject(var randomText:String,var title :String) {
}
We have all things set to create a recyclerView, so now we will define recyclerView in Fragment like
fragment_home.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/text_home"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:layout_marginTop="8dp"
android:layout_marginEnd="8dp"
android:textAlignment="center"
android:textSize="20sp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<androidx.recyclerview.widget.RecyclerView
android:id="#+id/recycler_view_home"
android:layout_width="match_parent"
android:layout_height="0dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="#+id/text_home" />
</androidx.constraintlayout.widget.ConstraintLayout>
Finally Inside kotlin class setting up a recyclerView
HomeFragment.kt
(i'm not including the irrelevant methods and variable that you have in this fragment, please consider them)
class HomeFragment : Fragment() {
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?): View? {
val root = inflater.inflate(R.layout.fragment_home, container, false)
val textView: TextView = root.findViewById(R.id.text_home)
val recyclerView: RecyclerView = root.findViewById(R.id.recycler_view_home)
callAPIDemo(textView)
setUpRecyclerView(recyclerView);
return root
}
private fun setUpRecyclerView(recyclerView: RecyclerView) {
// first our data list that can come from API/Database
// for now i'm just manually creating the list in getDummyData()
val list: MutableList<DataModelObject> = getDummyData()
// created an adapter object by passing the list object
var myAdapter = MyAdapter(list)
//it's a layoutManager, that we use to tell recyclerview in which fashion we want to show list,
// default is vertical even if you don't pass it and just pass the activity , and we don't want to reversing it so false
// by customizing it you can create Grids,vertical and horizontal lists, even card swipe like tinder
val linearLayoutManager = LinearLayoutManager(activity,LinearLayoutManager.VERTICAL,false)
// just set the above layout manager on RecyclerView
recyclerView.layoutManager = linearLayoutManager
//and give it a adapter for data
recyclerView.adapter = myAdapter
}
private fun getDummyData(): MutableList<DataModelObject> {
val list: MutableList<DataModelObject> = mutableListOf(
DataModelObject("dfgdfg", "title 1"),
DataModelObject("tyuityui", "title 2"),
DataModelObject("yuti", "title 3"),
DataModelObject("uY9NOrc-=s180-rw", "title 4"),
DataModelObject("logo_color_272x92dp.png", "title 5"),
DataModelObject("NOVtP26EKH", "title 6"),
DataModelObject("googlelogo_color", "title 7"),
DataModelObject("sNOVtP26EKHePkwBg-PkuY9NOrc-", "title 8")
)
return list
}
}
At first create a single item xml for each item of your recycler.
Then add the recycler in your fragment xml:
<androidx.recyclerview.widget.RecyclerView
android:id="#+id/recycler"
android:layout_width="match_parent"
android:layout_width="wrap_content" />
Then create an adapter class as shown below:
class RecyclerAdapter(List) :
RecyclerView.Adapter<RecyclerAdapter.RecyclerViewHolder>() {
//Binding data for each tile
override fun onBindViewHolder(holder: RecyclerViewHolder, position: Int) {
}
//Creation of view holder for each tile
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerViewHolder {
val inflater = LayoutInflater.from(parent.context)
context = parent.context
return RecyclerViewHolder(inflater, parent)
}
//View Holder for each item in recycler
inner class RecyclerViewHolder(inflater: LayoutInflater, parent: ViewGroup) :
RecyclerView.ViewHolder(inflater.inflate(R.layout.single_item, parent, false)) {
}
}
Then use the adapter like below in fragment:
val adapter = RecyclerViewAdapter(List)
recyclerview.adapter = adapter
Hope this helps
Related
i have the xml with recyclerview, id = list_chat
<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=".ui.chats.ChatsFragment">
<androidx.recyclerview.widget.RecyclerView
android:id="#+id/list_chat"
android:layout_width="match_parent"
android:layout_height="match_parent">
</androidx.recyclerview.widget.RecyclerView>
</androidx.constraintlayout.widget.ConstraintLayout>
my Fragment where will be recyclerview with function uploadList which put data in my array for recyclerView
class ChatsFragment : Fragment() {
var list: MutableList<Chat> = ArrayList()
lateinit var adapter: ChatListAdapter
lateinit var manager: RecyclerView.LayoutManager
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
uploadList()
Log.e("TAG", list.toString())
}
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
val view: View = inflater.inflate(R.layout.fragment_chats, container, false)
uploadList()
manager = LinearLayoutManager(activity)
list_chat.layoutManager = manager
adapter = ChatListAdapter(list)
list_chat.adapter = adapter
return view
}
private fun uploadList(){
list.add(Chat("11111", "11111"))
list.add(Chat("11222221", "133311"))
list.add(Chat("1122221", "114444411"))
list.add(Chat("112222211", "5555555"))
}
}
xml with item for list. ImageView is no matter for now. Need only two textView
<?xml version="1.0" encoding="utf-8"?>
<layout
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
xmlns:android="http://schemas.android.com/apk/res/android">
<LinearLayout
android:id="#+id/item_list"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal">
<de.hdodenhof.circleimageview.CircleImageView
android:id="#+id/chatImage"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="#mipmap/ic_launcher"/>
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical"
android:layout_marginStart="5sp">
<TextView
android:id="#+id/nameChat"
android:text="2222222222222"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
<TextView
android:id="#+id/infoChat"
android:text="11111111"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
</LinearLayout>
</LinearLayout>
</layout>
and my custom Adapter
class ChatListAdapter(private val myDataset: MutableList<Chat>) :RecyclerView.Adapter<ChatListAdapter.ViewHolder>() {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
val layoutInflater = LayoutInflater.from(parent.context)
.inflate(R.layout.item_list, parent, false)
return ViewHolder(layoutInflater)
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
holder.bind(myDataset[position])
}
override fun getItemCount() = myDataset.size
class ViewHolder(v: View) : RecyclerView.ViewHolder(v){
private val nameChat: TextView = itemView.findViewById(R.id.nameChat)
private val infoChat: TextView = itemView.findViewById(R.id.infoChat)
fun bind(chat: Chat){
nameChat.text = chat.name
infoChat.text = chat.info
}
}
}
and i catch java.lang.IllegalStateException: list_chat must not be null here list_chat.layoutManager = manager
help me with it, i have list_chat only in fragment_chats, so it is right recyclerview.
Because help me pls, I dont know what is the problem
Initialize layout manager and adapter in onViewCreated() Method
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
uploadList()
manager = LinearLayoutManager(activity)
list_chat.layoutManager = manager
adapter = ChatListAdapter(list)
list_chat.adapter = adapter
})
Are you using kotlinx.android.synthetic to find your views? If so, these are not valid until after onCreateView() returns, and so you cannot use them within onCreateView().
Move your code to onViewCreated(), or switch to manually calling view.findViewById() from inside onCreateView().
Approach 1 :
Create a recyclerview object :
val rv = view.findViewById(your rv id)
and assign the layout manager and adapter to it.
Approach 2 :
In your layout file, in recyclerview add this line :
<androidx.recyclerview.widget.RecyclerView
<!-- your default lines -->
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
/>
and assign the adapter in onViewCreated method.
I am brand new in Android/kotlin development. I created my very first app with a recyclerView to display the folders and files of the phone. I put the recyclerView into a segment, created my data structure and adapter. Assembled together. It works - or seems to work. But the problem is that when I try to scroll the list, the initial state remains "ther" and the list content starts to scroll. Like it was two different layers. I have no clue where to find the problem. Never heard about such a bug like this. Please give me advice, some keywords where to dig and find the solution. Thanks!
class BrowseFileFragment : Fragment() {
private lateinit var attachedCtx : Context
override fun onAttach(context: Context) {
super.onAttach(context)
this.attachedCtx = context
}
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,savedInstanceState: Bundle?): View?
{
var view : View = inflater.inflate(R.layout.fragment_browse_file, container, false)
val modelFactory = FileBrowseModelFactory()
val data = modelFactory.create()
bindModelToView(data, view)
return view
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
}
private fun bindModelToView(data: BrowseSettings, view: View)
{
var adapter = MyFileAdapter(data.FoldersAndFiles, this.attachedCtx)
var recyclerView = view.findViewById<RecyclerView>(R.id.fileItemView)
var linearLayoutManager = LinearLayoutManager(this.attachedCtx)
recyclerView.layoutManager = linearLayoutManager
recyclerView.adapter = adapter
var folderLabel = view.findViewById<TextView>(R.id.folderName)
folderLabel.text = data.CurrentFolder
}
}
The segment layout
<?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=".BrowseFileFragment">
<TextView
android:id="#+id/folderName"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="5dp"
android:text="#string/hello_first_fragment"
android:textAlignment="viewStart"
android:textAppearance="#style/TextAppearance.AppCompat.Display1"
android:textStyle="bold"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<androidx.recyclerview.widget.RecyclerView
android:id="#+id/fileItemView"
android:layout_width="match_parent"
app:layout_constrainedHeight="true"
android:layout_height="0dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="#+id/folderName"
app:layout_constraintStart_toStartOf="#+id/folderName"
app:layout_constraintTop_toBottomOf="#+id/folderName" />
</androidx.constraintlayout.widget.ConstraintLayout>
The list line layout
<?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="60dp">
<TextView
android:background="#color/cardview_shadow_start_color"
android:id="#+id/itemName"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintBottom_toTopOf="parent"
android:padding="5dp"
android:textSize="30dp"
android:textStyle="bold" />
</androidx.constraintlayout.widget.ConstraintLayout>
And finally my adapter:
class MyFileAdapter(private val items: List<FileItem>, private val context: Context)
: RecyclerView.Adapter<MyFileAdapter.MyViewHolder>()
{
class MyViewHolder (itemView: View) :RecyclerView.ViewHolder(itemView), View.OnClickListener {
init {
itemView.setOnClickListener(this)
}
fun bindItem(f: FileItem) {
var name: TextView = itemView.findViewById(R.id.itemName) as TextView
name.text = f.Name
}
}
override fun getItemCount() = items.size
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MyViewHolder {
val view = LayoutInflater
.from(context)
.inflate(R.layout.recyclerview_filteitem_row, parent, false)
return MyViewHolder(view)
}
override fun onBindViewHolder(holder: MyViewHolder, position: Int) {
var item = items\[position\]
holder!!.bindItem( item )
}
}
It found out that as the whole list view is saved as a segment, this segment loads "automatically" and was loaded programatically also, and the two "loads" causes this thing. :( Thanks for your time guys.
I want to achieve a layout as shown in the Android Studio preview (left). However if executed in the emulator, only the button is visible and the RecyclerView is not visible/populated (right).
The XML code:
<?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"
style="#style/AppTheme">
<androidx.recyclerview.widget.RecyclerView
android:id="#+id/list"
android:name="com.example.app.ItemFragment"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginLeft="16dp"
android:layout_marginRight="16dp"
app:layoutManager="LinearLayoutManager"
tools:context=".ItemFragment"
tools:listitem="#layout/fragment_item"
android:scrollbars="vertical"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<com.google.android.material.floatingactionbutton.FloatingActionButton
android:id="#+id/floatingActionButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="bottom|center_horizontal"
android:layout_margin="#dimen/fab_margin"
android:clickable="true"
android:focusable="true"
android:src="#android:drawable/ic_input_add"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
However if the RecyclerView is alone in the fragment the list is populated (but of course the action button is not showing). Code see below. And yes, my list which should be shown is not empty.
<?xml version="1.0" encoding="utf-8"?>
<androidx.recyclerview.widget.RecyclerView 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/list"
android:name="com.example.app.ItemFragment"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginLeft="16dp"
android:layout_marginRight="16dp"
app:layoutManager="LinearLayoutManager"
tools:context=".ItemFragment"
tools:listitem="#layout/fragment_item" >
</androidx.recyclerview.widget.RecyclerView>
I have already tried using RelativeLayout and FrameLayout, but I still get the same result. The same behavior occurs if I use e.g. a TextView instead of the action button.
--- Requested additional info ---
Adapter class (automatically generated by Android Studio, template):
class MyItemRecyclerViewAdapter(
private val mValues: List<DummyItem>,
private val mListener: OnListFragmentInteractionListener?
) : RecyclerView.Adapter<MyItemRecyclerViewAdapter.ViewHolder>() {
private val mOnClickListener: View.OnClickListener
init {
mOnClickListener = View.OnClickListener { v ->
val item = v.tag as DummyItem
// Notify the active callbacks interface (the activity, if the fragment is attached to
// one) that an item has been selected.
mListener?.onListFragmentInteraction(item)
}
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
val view = LayoutInflater.from(parent.context)
.inflate(R.layout.fragment_item, parent, false)
return ViewHolder(view)
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
val item = mValues[position]
holder.mIdView.text = item.id
holder.mContentView.text = item.content
with(holder.mView) {
tag = item
setOnClickListener(mOnClickListener)
}
}
override fun getItemCount(): Int = mValues.size
inner class ViewHolder(val mView: View) : RecyclerView.ViewHolder(mView) {
val mIdView: TextView = mView.item_number
val mContentView: TextView = mView.content
override fun toString(): String {
return super.toString() + " '" + mContentView.text + "'"
}
}
}
List fragment (automatically generated by Android Studio, template):
class ItemFragment : Fragment() {
// TODO: Customize parameters
private var columnCount = 1
private var listener: OnListFragmentInteractionListener? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
arguments?.let {
columnCount = it.getInt(ARG_COLUMN_COUNT)
}
}
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
val view = inflater.inflate(R.layout.fragment_item_list, container, false)
// Set the adapter
if (view is RecyclerView) {
with(view) {
layoutManager = when {
columnCount <= 1 -> LinearLayoutManager(context)
else -> GridLayoutManager(context, columnCount)
}
adapter = MyItemRecyclerViewAdapter(DummyContent.ITEMS, listener)
}
}
return view
}
override fun onAttach(context: Context) {
super.onAttach(context)
if (context is OnListFragmentInteractionListener) {
listener = context
} else {
throw RuntimeException(context.toString() + " must implement OnListFragmentInteractionListener")
}
}
override fun onDetach() {
super.onDetach()
listener = null
}
/**
* This interface must be implemented by activities that contain this
* fragment to allow an interaction in this fragment to be communicated
* to the activity and potentially other fragments contained in that
* activity.
*
*
* See the Android Training lesson
* [Communicating with Other Fragments](http://developer.android.com/training/basics/fragments/communicating.html)
* for more information.
*/
interface OnListFragmentInteractionListener {
// TODO: Update argument type and name
fun onListFragmentInteraction(item: DummyItem?)
}
companion object {
// TODO: Customize parameter argument names
const val ARG_COLUMN_COUNT = "column-count"
// TODO: Customize parameter initialization
#JvmStatic
fun newInstance(columnCount: Int) =
ItemFragment().apply {
arguments = Bundle().apply {
putInt(ARG_COLUMN_COUNT, columnCount)
}
}
}
}
Try this for your layout
<?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"
style="#style/AppTheme"
android:layout_width="match_parent"
android:layout_height="match_parent">
<androidx.recyclerview.widget.RecyclerView
android:id="#+id/list"
android:name="com.example.app.ItemFragment"
android:layout_width="0dp"
android:layout_height="0dp"
android:scrollbars="vertical"
app:layoutManager="LinearLayoutManager"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:context=".ItemFragment"
tools:listitem="#layout/fragment_item" />
<com.google.android.material.floatingactionbutton.FloatingActionButton
android:id="#+id/floatingActionButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="bottom|center_horizontal"
android:layout_margin="#dimen/fab_margin"
android:clickable="true"
android:focusable="true"
android:src="#android:drawable/ic_input_add"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
Inside ItemFragment.kt replace onCreateView with
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
return inflater.inflate(R.layout.fragment_item_list, container, false)
}
After that go ahead and implement onViewCreated as such
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
with(list) {
layoutManager = when {
columnCount <= 1 -> LinearLayoutManager(requireContext())
else -> GridLayoutManager(requireContext(), columnCount)
}
adapter = MyItemRecyclerViewAdapter(DummyContent.ITEMS)
}
}
This should fix your problem because the AS template is assuming that the rootview will be a RecyclerView and is treating the whole layout as such.
I am trying to make a PagerView inside a RecyclerView using androidx widgets, here is my code:
ManageCategories.jt (Fragment)
class ManageCategories : Fragment() {
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
//TODO make json for categories
val root = inflater.inflate(R.layout.fragment_manage_categories, container, false)
val array = arrayOf("this", "is", "a", "test")
val viewAdapter = CategoryRecyclerAdapter(array)
val templateList = root.findViewById<RecyclerView>(R.id.template_manage_category)
val itemDecoration: RecyclerView.ItemDecoration = DividerItemDecoration(context, DividerItemDecoration.VERTICAL)
templateList.addItemDecoration(itemDecoration)
templateList.adapter = viewAdapter
return root
}
}
fragment_manage_categories.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=".ui.templates.templates.ManageTemplates">
<androidx.recyclerview.widget.RecyclerView
android:id="#+id/template_manage_category"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layoutManager="LinearLayoutManager" />
</androidx.constraintlayout.widget.ConstraintLayout>
The fragment gets inflates and sets a RecyclerAdapter
CategoryRecyclerAdapter.kt
class CategoryRecyclerAdapter(private val myDataset: Array<String>) :
RecyclerView.Adapter<CategoryViewHolder>() {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): CategoryViewHolder {
val layout = LayoutInflater.from(parent.context).inflate(R.layout.list_category, parent, false)
return CategoryViewHolder(layout)
}
override fun onBindViewHolder(holder: CategoryViewHolder, position: Int) {
holder.itemView.setOnClickListener {
when (position) {
0 -> {
Log.d("TEXT", "backup")
}
}
}
//TODO uncomment holder.textView.text = myDataset[position]
}
override fun getItemCount() = myDataset.size
}
CategoryViewHolder.kt
class CategoryViewHolder(v: View) : RecyclerView.ViewHolder(v) {
init {
val vp = v.findViewById<ViewPager>(R.id.category_pager)
val pagerAdapter = CategoryPagerAdapter()
vp.adapter = pagerAdapter
}
}
list_category.xml
<?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="wrap_content"
android:orientation="horizontal"
android:background="?android:attr/selectableItemBackground" >
<androidx.viewpager.widget.ViewPager
android:id="#+id/category_pager"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</LinearLayout>
The RecyclerAdapter inflates each recycled view (4 times) with some dummy data ("this" "is" "a" "test")
when the ViewHolder is called it initializes the PagerView and sets its adapter (I have tried to do this in a ViewHolder and the RecyclerAdapters onCreateViewHolder() method but it made no difference)
CategoryPagerAdapter.kt
class CategoryPagerAdapter: PagerAdapter() {
override fun isViewFromObject(view: View, `object`: Any): Boolean {
return view === `object`
}
override fun getCount() = 2
override fun instantiateItem(collection: ViewGroup, position: Int): ViewGroup {
val view: CategoryModel = CategoryModel.values().get(position)
val inflater = LayoutInflater.from(collection.context)
val layout = inflater.inflate(view.layoutResId, collection, false) as ViewGroup
collection.addView(layout)
return layout
}
override fun destroyItem(collection: ViewGroup, position: Int, view: Any) {
collection.removeView(view as View?)
}
}
CategoryModel.kt
enum class CategoryModel(val layoutResId: Int) {
TEXT(R.layout.category_list_item),
BUTTONS(R.layout.button_pager);
}
category_list_item.kt
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:background="?android:attr/selectableItemBackground" >
<TextView
android:id="#+id/text_item"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="hello"
android:textSize="15sp"
android:padding="15dp" />
</LinearLayout>
button_pager.xml
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:background="?android:attr/selectableItemBackground" >
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="world"
android:textSize="15sp"
android:padding="15dp" />
</LinearLayout>
The PagerAdapter then begins to inflate views with static text in place of RecyclerView Views with 2 PagerViews both of which are holding dummy text ("hello" "world"). This doesn't appear to work, I cant figure out why.
I have put the code through the debugger
the Recycler is being inflated 4 times
the Pager is being inflated 2 times with the correct layout files each time
yet I can't see any text, and when I try to findViewById for the TextViews the debugger returns false.
Whats going on? why can't I see the text?
EDIT:
To test I decided to remove the recycler so that only the pager was in use:
ManageCategories.jt (Fragment)
class ManageCategories : Fragment() {
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
//TODO make json for categories
val root = inflater.inflate(R.layout.list_category, container, false)
val pagerAdapter = CategoryPagerAdapter()
val pager: ViewPager = root.findViewById(R.id.category_pager)
pager.adapter = pagerAdapter
return root
}
}
This worked as expected and the Pager layout worked fine. In a seperate test I removed the Pager layout and that also worked fine. I just can't get the two to work together.
My current working theory is that I am trying to inflate the view within CategoryViewHolder through CategoryPagerAdapter something I found out about RecyclerView was that it doesn't like inflating views within its ViewHolder. I just don't know where to put it.
EDIT2:
I have moved the code that initializes CategoryPagerAdapter into onCreateViewHolder and/or onBindViewHolder method of CategoryRecyclerAdapter.
After further investigation I have found that instantiateItem() is returning a proper ViewGroup of inflated views insideCategoryPagerAdapter. However, for whatever reason when I try to inspect the object/views outside of the class(CategoryRecyclerAdapter) pagerAdapter they appear to be emtpy. I am confident that when I can figure out why it is getting null that the functionality will be working as expected, but I can't figure out why. Please help.
EDIT3:
I figured that since the pager was inflating the views, according to the values in the debugger. But then when inspecting the pager inside the recycler that views were no longer present, naturally I thought they were using different inflaters.
I compared the two inflaters used in the Logs and they are both the same object, so its not that.
I managed to get the functionality by using a ViewPager2 since ViewPager is no longer actively being worked on;
class CategoryPagerAdapter(private val model: Array<CategoryModel>): RecyclerView.Adapter<CategoryPagerViewHolder>() {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): CategoryPagerViewHolder {
val view = model[viewType].layoutResId
val layout = LayoutInflater.from(parent.context).inflate(view, parent, false)
return CategoryPagerViewHolder(layout)
}
override fun getItemViewType(position: Int): Int = position
override fun getItemCount() = model.size
override fun onBindViewHolder(holder: CategoryPagerViewHolder, position: Int) {
holder.itemView.setOnClickListener {
when (position) {
0 -> {
Log.d("TEXT", "backup")
}
}
}
}
}
class CategoryPagerViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView)
Here is a barely working version of the code. Text isn't properly being updated yet.
I'm currently trying to create a dynamic header with a recyclerView. I have written the ListAdapter aswell as the ViewHolder. The custom list elements are added, and also the numer of the elements within the list is correct, but somehow it's not showing the object data, but only the dummyText that was added at the layoutdesign.
HeaderlistAdapter:
class HeaderListAdapter(val context: Context, val headers: List<CustomHeader>) : RecyclerView.Adapter<HeaderViewHolder>() {
override fun onCreateViewHolder(parent: ViewGroup?, viewType: Int): HeaderViewHolder {
val view = LayoutInflater.from(context).inflate(R.layout.ui_basic_custom_list_element_header, parent, false)
return HeaderViewHolder(view)
}
override fun getItemCount(): Int {
return headers.size
}
override fun onBindViewHolder(holder: HeaderViewHolder?, position: Int) {
holder?.bindHeader(headers[position])
}
fun setFocus(step:UIStep)
{
for(header in headers)
header.Active=header.MainContent==step
notifyDataSetChanged()
}
}
HeaderViewHolder:
class HeaderViewHolder:RecyclerView.ViewHolder{
#Bind(R.id.ui_adapter_main) var mainText:TextView?=null
#Bind(R.id.ui_adapter_additional) var additionalText:TextView?=null
#Bind(R.id.ui_adapter_layout) var layout:LinearLayout?=null
constructor(itemView: View): super(itemView){
ButterKnife.bind(this,itemView)
}
fun bindHeader(header:CustomHeader){
if(header.Active) {
mainText?.text = header.MainContent.description
additionalText?.text=header.AdditionalText
layout?.setBackgroundColor(R.color.colorBackgroundActive.toInt())
}
else{
mainText?.text=header.MainContent.number.toString()
additionalText?.text=""
layout?.setBackgroundColor(R.color.colorBackgroundInactive.toInt())
}
}
}
Here is, how the listAdapter looks within the view
<android.support.v7.widget.RecyclerView
android:id="#+id/ui_basic_lv_header"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="horizontal"
android:background="#color/colorBackgroundInactive"
android:layout_weight="1" />
Below here you se the xml of the custom element
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:orientation="horizontal"
android:background="#color/colorBackgroundInactive"
android:textColor="#color/colorHeaderFont"
android:id="#+id/ui_adapter_layout">
<TextView
android:id="#+id/ui_adapter_main"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:text="Main Info"
android:textSize="36sp"/>
<TextView
android:id="#+id/ui_adapter_additional"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:text="additional"
android:gravity="center_vertical"/>
Looks like there was something wrong with the
#Bind
I replaced that by using findViewById within the binHeader function:
fun bindHeader(header:CustomHeader){
val mainText = itemView.findViewById<TextView>(R.id.ui_adapter_main)
val additionalText = itemView.findViewById<TextView>(R.id.ui_adapter_additional)
val layout = itemView.findViewById<LinearLayout>(R.id.ui_adapter_layout)
if(header.Active) {
mainText?.text = header.MainContent.description
additionalText?.text=header.AdditionalText
layout?.setBackgroundColor(R.color.colorBackgroundActive.toInt())
}
else{
mainText?.text=header.MainContent.number.toString()
additionalText?.text=""
layout?.setBackgroundColor(R.color.colorBackgroundInactive.toInt())
}
}
contentList may be null ,if contentList is Null, the method following '?' will not execute . and after setting the adapter can not call notifyDataSetChanged ;
You should set layout manager for recyclerview
myRecyclerView.setLayoutManager(linearLayoutManagerVertical);