I was working on an Android project that features two fragments on the main activity with one RecyclerView in each of them. It was looking good and functioning as intended, then somewhere along the way something broke and now neither is populating with anything. I'm not sure how to even begin to debug this, as everything looks correct to me. I will just focus on one of the RecyclerViews for the code below. The other one is imported from another project, so it has even less reason to break as I didn't code it! If you need any more information let me know as I am learning as I go here.
I have tried cleaning the project, rebuilding, restarting Android Studio in case it was a weird cache issue. I have combed over the code but nothing jumps out at me and Android Studio is not raising any errors. The project builds and loads but the Recycler Views are devoid of content.
build.gradle
dependencies {
implementation 'androidx.legacy:legacy-support-v4:1.0.0'
coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:1.1.5'
implementation 'androidx.fragment:fragment-ktx:1.4.1'
implementation 'androidx.core:core-ktx:1.7.0'
implementation 'androidx.appcompat:appcompat:1.4.1'
implementation 'com.google.android.material:material:1.5.0'
implementation 'androidx.constraintlayout:constraintlayout:2.1.3'
implementation 'androidx.recyclerview:recyclerview:1.2.1'
implementation project(':crunchycalendar')
testImplementation 'junit:junit:4.13.2'
implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.4.1"
debugImplementation 'androidx.fragment:fragment-testing:1.4.1'}
CardsFragment.kt
class CardsFragment : Fragment(R.layout.fragment_cards) {
var sampleDidits : Array<Didit> = arrayOf(
Didit("Walk 15 minutes around the block with your dog", "Grab the earbuds and get outside", R.drawable.footsteps),
Didit("Meditate 20 minutes", "This flavor text is too long man chill out", R.drawable.meditation),
Didit("Take your meds", "Withdrawal ain't fun", R.drawable.pill_bottle)
)
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
// Inflate the layout for this fragment
val binding = FragmentCardsBinding.inflate(layoutInflater, container, false)
// Add the following lines to create RecyclerView
val recyclerView = binding.cardsRecyclerView
recyclerView.setHasFixedSize(true);
val snapHelper: SnapHelper = LinearSnapHelper()
snapHelper.attachToRecyclerView(recyclerView)
val layoutManager = LinearLayoutManager(this.context)
layoutManager.orientation = LinearLayoutManager.HORIZONTAL
recyclerView.layoutManager = layoutManager;
recyclerView.adapter = CardViewAdapter(sampleDidits);
return binding.root
}
}
fragment_cards.xml
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:tools="http://schemas.android.com/tools"
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<androidx.constraintlayout.widget.ConstraintLayout
android:id="#+id/cf_constraint"
android:layout_width="match_parent"
android:layout_height="match_parent">
<androidx.recyclerview.widget.RecyclerView
android:id="#+id/cardsRecyclerView"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scrollbars="horizontal"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>
CardViewAdapter.kt
class CardViewAdapter(private val didit_tasks: Array<Didit>) :
RecyclerView.Adapter<CardViewAdapter.ViewHolder>() {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
val cv = LayoutInflater.from(parent.context)
.inflate(R.layout.didit_card, parent, false) as CardView
return ViewHolder(cv)
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
// This is where data is added to the card view
val cardView = holder.cardView
val mainText = cardView.findViewById<View>(R.id.maintext) as TextView
val streakText = cardView.findViewById<View>(R.id.streak_text) as TextView
val flavorText = cardView.findViewById<View>(R.id.flavorText) as TextView
val diditIcon = cardView.findViewById<View>(R.id.didit_icon) as ImageView
val drawable = ContextCompat.getDrawable(cardView.context, didit_tasks[position].icon)
val streakString = didit_tasks[position].currentStreakString + " Day Streak"
diditIcon.setImageDrawable(drawable)
mainText.text = didit_tasks[position].mainText
flavorText.text = didit_tasks[position].flavorText
streakText.text = streakString
}
override fun getItemCount(): Int {
return didit_tasks.size
}
class ViewHolder(val cardView: CardView) : RecyclerView.ViewHolder(
cardView
)
Update:
It appears to be an issue not with my Recycler Views but with my Fragments. None of the fragments are loading up and I'm not sure why.
The issue seemed to be within my MainActivity.kt file. Under the onCreate function I had both setContentView(R.layout.activity_main)
AND
binding = DataBindingUtil.setContentView(this, R.layout.activity_main)
Removing the former line solved the issue so all fragments loaded properly
Related
I have recyerlView. I loaded data from the server. When it initially loads it height is normal, but when I go to the next activity through item click. And come back it slowly increasing the height of the child. I tried to debug this and found that onResume api call causing the issue. But What I am doing wrong in layout I don't get it.
FirstLayout.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">
<androidx.recyclerview.widget.RecyclerView
android:id="#+id/reyclerview"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
FirstActivity.kt
class FirstActivity : BaseActivity() {
lateinit var binding: FirstLayoutActivityLayoutBinding
private val viewModel: FirstViewModel by inject()
private var listAdapter: ListAdapter? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setupViewModel()
binding = FirstLayoutActivityLayoutBinding.inflate(layoutInflater)
setContentView(binding.root)
}
private fun setupViewModel() {
viewModel.livedata.observe(this, { list ->
setupAdapter(list)
})
}
private fun setupAdapter(list: List<String>) {
initializeAdapter(list)
listAdapter?.updateItemsList(list)
binding.recyclerView.apply {
addItemDecoration(HeaderItemDecoration(context))
val itemDecorator = DividerItemDecoration(context, DividerItemDecoration.VERTICAL)
itemDecorator.setDrawable(ContextCompat.getDrawable(context, R.drawable.divider)!!)
addItemDecoration(itemDecorator)
adapter = listAdapter
}
}
private fun initializeAdapter(list: List<String>) {
listAdapter = ListAdapter(list.toMutableList())
}
override fun onResume() {
super.onResume()
viewModel.fetchItem() // noraml retrofit call
}
}
HeaderItemDecoration is used from this answer.
ListAdapter.kt
class ListAdapter(private val list: MutableList<String>) : RecyclerView.Adapter<Adapter.MyViewHolder>() {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MyViewHolder {
return MyViewHolder(
ListLayoutBinding.inflate(
LayoutInflater.from(parent.context),
parent,
false
)
)
}
override fun onBindViewHolder(holder: MyViewHolder, position: Int) {
holder.bindItem(list[position])
}
override fun getItemCount(): Int {
return list.size
}
inner class MyViewHolder(private val binding: ListLayoutBinding) : RecyclerView.ViewHolder(binding.root) {
fun bindItem(s: String) {
binding.cool.text = s
}
}
}
ListLayout.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:id="#+id/root"
android:background="#color/red"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView
android:id="#+id/cool"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
I am adding my view to my Youtube Link. Please look at what the issue is. Thanks
I didn't add any logic to increase the height
looks like a problem with multiple ItemDecorations, which are set every time new data is fetched. to be exact: multiple DividerItemDecorations, which adds small padding to the items (for dividing them). note methods name starts with add..., so every time you are adding new, old stays there and every instance of this divider is adding some padding. if you would implement e.g. pull-to-refresh or auto-refresh in background every e.g. 10 secs then every refresh GUI method call (setupAdapter) would add some space without leaving Activity. currently you are fetching data only once, in onResume, so every move-activity-to-foreground action will add one divider (when data will be fetched properly)
move this part of code to onCreate for setting dividers only once, thats proper place for some additional styling by code
binding.recyclerView.apply {
addItemDecoration(HeaderItemDecoration(context))
val itemDecorator = DividerItemDecoration(context, DividerItemDecoration.VERTICAL)
itemDecorator.setDrawable(ContextCompat.getDrawable(context, R.drawable.divider)!!)
addItemDecoration(itemDecorator)
}
and inside your setupAdapter method set only adapter to RecyclerView, don't style it (multiple times)
private fun setupAdapter(list: List<String>) {
initializeAdapter(list)
listAdapter?.updateItemsList(list)
binding.recyclerView.adapter = listAdapter
}
I know this question has been posed a hundred times, which only makes it more frustrating for me.
I basically took this code from Camera2Basic in https://github.com/android/camera-samples.
Here's what I've built on top from that project so far:
<!-- selector_fragment.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">
<androidx.recyclerview.widget.RecyclerView
android:id="#+id/camera_list"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
/>
</androidx.constraintlayout.widget.ConstraintLayout>
class SelectorFragment : Fragment() {
private var binding: SelectorFragmentBinding? = null
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
binding = SelectorFragmentBinding.inflate(inflater, container, false)
return binding!!.root
}
#SuppressLint("MissingPermission")
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
val cameraManager =
requireContext().getSystemService(Context.CAMERA_SERVICE) as CameraManager
val configList = enumerateCameras(cameraManager)
val layoutId = android.R.layout.simple_list_item_1
val genericAdapter = GenericListAdapter(configList, itemLayoutId = layoutId) { innerView, item, _ ->
innerView.findViewById<TextView>(android.R.id.text1).text = item.title
innerView.setOnClickListener {
Navigation.findNavController(requireActivity(), R.id.nav_host_fragment)
.navigate(SelectorFragmentDirections.actionSelectorToCamera(
item.cameraId, item.format))
}
}
val cameraList: RecyclerView = binding!!.cameraList
cameraList.adapter = genericAdapter
val items = cameraList.adapter!!.itemCount
cameraList.adapter!!.notifyDataSetChanged()
}
...
}
/** Type helper used for the callback triggered once our view has been bound */
typealias BindCallback<T> = (view: View, data: T, position: Int) -> Unit
/** List adapter for generic types, intended used for small-medium lists of data */
class GenericListAdapter<T>(
private val dataset: List<T>,
private val itemLayoutId: Int? = null,
private val itemViewFactory: (() -> View)? = null,
private val onBind: BindCallback<T>
) : RecyclerView.Adapter<GenericListAdapter.GenericListViewHolder>() {
class GenericListViewHolder(val view: View) : RecyclerView.ViewHolder(view)
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) = GenericListViewHolder(when {
itemViewFactory != null -> itemViewFactory.invoke()
itemLayoutId != null -> {
LayoutInflater.from(parent.context)
.inflate(itemLayoutId, parent, false)
}
else -> {
throw IllegalStateException(
"Either the layout ID or the view factory need to be non-null")
}
})
override fun onBindViewHolder(holder: GenericListViewHolder, position: Int) {
if (position < 0 || position > dataset.size) return
onBind(holder.view, dataset[position], position)
}
override fun getItemCount() = dataset.size
}
When I go into the debugger, none of the Adapter methods are being called.
However, when I turn the screen the Adapter is called, and the app starts behaving as expected.
The problem also occurs when I use the old bindings -- i.e. view.camera_list.adapter = genericAdapter.
There shouldn't be any async happening, and enumerateCameras (omitted for brevity) returns as expected. Calling adapter.getItemCount directly returns as expected.
Adding the cameraList.adapter.notifyDataSetChanged() method seems to have no effect.
I've tried removing app:layoutManager and setting cameraList.layoutManager = LinearLayoutManager(requireContext()) directly; in fact, I've tried both with the same results.
Changing to a FrameLayout from ConstraintLayout makes no difference.
I've updated all my androidx dependencies and all other relevant dependencies.
Again, turning the device and triggering a relayout causes the list to populate as expected. I'm not sure what mechanism is calling adapter methods.
I'm going to try to roll my own Adapter in the mean time instead of using this one.
Any leads are appreciated.
The answer was in my navigation components. It was a combination of my misunderstanding, shotgun coding, and missing a simple detail. The default NavController/NavHostFragment depends on an ActionBar, which was not present in the theme I had chosen for a previous test, Theme.AppCompat.NoActionBar.
I was totally off-base calling out the RecyclerView/Adapter, since the layout wasn't going to the screen at all. After setting up the NavBar to work according to the navigation components tutorial, the output is as expected.
Okay so I'm experimenting with Two-Way Data binding right now, logically I think everything is perfect in the code, but somehow I keep getting the same error whenever I run the app: "A failure occurred while executing org.jetbrains.kotlin.gradle.internal.KaptExecution" . Basically I'm trying to hide ImageView visibility if there is no data in my database, using Data Binding ofc.
fragment_list.xml (Binding Layout)
<?xml version="1.0" encoding="utf-8"?>
<layout 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">
<data>
<variable
name="listViewModel"
type="com.jovanovic.stefan.tododemo.fragments.list.ListViewModel" />
</data>
<androidx.constraintlayout.widget.ConstraintLayout
android:id="#+id/listLayout"
... >
<ImageView
android:id="#+id/no_data_imageView"
emptyDatabase="#={listViewModel.emptyDatabase}"
... />
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>
ListViewModel (ViewModel for my Fragment)
class ListViewModel: ViewModel() {
val emptyDatabase: MutableLiveData<Boolean> = MutableLiveData<Boolean>(true)
fun checkDatabase(toDoData: List<ToDoData>){
emptyDatabase.value = toDoData.isEmpty()
}
}
ListFragment
class ListFragment : Fragment(), SearchView.OnQueryTextListener {
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
val listViewModel = ViewModelProvider(this).get(ListViewModel::class.java)
val binding = FragmentListBinding.inflate(inflater, container, false)
binding.lifecycleOwner = this
binding.listViewModel = listViewModel
val toDoViewModel= ViewModelProvider(this).get(ToDoViewModel::class.java)
// Observing LiveData object for Room DB which is reading all data
toDoViewModel.allData.observe(viewLifecycleOwner, Observer { data ->
// Using checkDatabase method from ListViewModel
listViewModel.checkDatabase(data)
})
return binding.root
}
BindingAdapter
#BindingAdapter("emptyDatabase")
#JvmStatic
fun emptyDatabase(view: View, emptyDatabase: MutableLiveData<Boolean>){
if(emptyDatabase.value == true){
view.visibility = View.VISIBLE
}else{
view.visibility = View.INVISIBLE
}
}
emptyDatabase="#={listViewModel.emptyDatabase}"
"=" is us only for android Model two way data binding."=" meaning if model update by get call its update view and if view update its update Model by set call .It's not applicable for databinding adaptor function
I remember that I had similar problems with kapt. Please clean your build and try again, this works for me. Also as per the scenario you described for emptyDatabase.value == true, view.visibility should be View.Insivisble.
After watching this video at (4:29) : https://youtu.be/TW9dSEgJIa8
I saw that I forgot to add this dependency for DataBinding:
//DataBinding
kapt "com.android.databinding:compiler:3.2.0-alpha10"
And even after I added that dependency, I still had an error, until I REMOVED "=" from "#={listViewModel.emptyDatabase}"
I know that there are plenty of similiar posts about similar issues as mine, I even followed this and this and other posts as well (I won't list every single post here though) and tried different things out, but I still can't make my recyclerview show anything on my Fragment.
I write an app mostly for learning purposes - something similar to this, just to get sense about how things work together. If you like to see the project, (at least what I've done so far), you can do this here.
I just want to show items on recyclerview. Currently, nothing from the recyclerview is shown in the Fragment a white background only :(.
I don't even get any errors sort of "No adapter attached; skipping layout" or "No LayoutManager attached; skipping layout". And I am sure this could be a really small issue, so could you please explain me what am I missing or doing wrong.
Thank you so much for your efforts.
Here is what I do:
The Adapter code:
class ActivitiesAdapter internal constructor(
context: Context
) : RecyclerView.Adapter<ActivitiesAdapter.ActivitiesViewHolder>() {
private val inflater: LayoutInflater = LayoutInflater.from(context)
private var activities = emptyList<DoItAgainEntity>()
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ActivitiesViewHolder {
val itemView = inflater.inflate(R.layout.recyclerview_item, parent, false)
return ActivitiesViewHolder(itemView)
}
override fun onBindViewHolder(holder: ActivitiesViewHolder, position: Int) {
val current = activities[position]
holder.activityItemView.text = current.engagement
}
internal fun setActivities(activities: List<DoItAgainEntity>) {
this.activities = activities
notifyDataSetChanged()
}
override fun getItemCount() = activities.size
inner class ActivitiesViewHolder(itemview: View) : RecyclerView.ViewHolder(itemview) {
val activityItemView: TextView = itemview.findViewById(R.id.textView)
}
}
The Fragment code:
class ShowDBEntriesFragment : Fragment() {
private lateinit var viewModel: ShowDBEntriesViewModel
private lateinit var layout: View
private lateinit var recyclerView: RecyclerView
private lateinit var adapter: ActivitiesAdapter
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
layout = inflater.inflate(R.layout.show_dbentries_fragment, container, false)
adapter = ActivitiesAdapter(context!!)
recyclerView = layout.findViewById(R.id.recyclerview)
recyclerView.addItemDecoration(
DividerItemDecoration(
context!!,
LinearLayoutManager.VERTICAL
)
)
recyclerView.layoutManager =
LinearLayoutManager(context!!, LinearLayoutManager.VERTICAL, false)
recyclerView.adapter = adapter
return layout
}
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
viewModel = ViewModelProviders.of(this).get(ShowDBEntriesViewModel::class.java)
fab.setOnClickListener {
val fragmentManager = (activity as MainActivity).supportFragmentManager
val fragmentTransaction = fragmentManager.beginTransaction()
val fragment = InsertNewEngagementFragment()
fragmentTransaction.replace(R.id.fragment_newEngagement, fragment)
fragmentTransaction.commit()
}
}
}
The xml code from recyclerview_item.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="match_parent"
android:orientation="vertical">
<TextView
android:id="#+id/textView"
style="#style/activity_title"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="#android:color/holo_orange_light" />
</LinearLayout>
You are not calling setActivities().
Your code will look like this:
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
layout = inflater.inflate(R.layout.show_dbentries_fragment, container, false)
adapter = ActivitiesAdapter(context!!)
recyclerView = layout.findViewById(R.id.recyclerview)
recyclerView.addItemDecoration(
DividerItemDecoration(
context!!,
LinearLayoutManager.VERTICAL
)
)
recyclerView.layoutManager =
LinearLayoutManager(context!!, LinearLayoutManager.VERTICAL, false)
recyclerView.adapter = adapter
// Create activities variable
adapter.setActivities(activities)
return layout
}
In recyclerview_item.xml
Change
android:layout_height="match_parent"
to
android:layout_height="wrap_content"
The item layout is probably taking entire layout space.
This is the answer of this issue. I don't OWN this code. This code was written from a genious with huge heart (and also a friend of mine) who understands how things work and writes beautiful code. It's a great honor of mine to learn from you, bro - thank you so much for your ultimate kindness and god-like Android knowledge you have! Keep it up like this.
And here it is - the right way of doing things:
The Adapter code:
class ActivitiesAdapter : RecyclerView.Adapter<ActivitiesViewHolder>() {
private var activities = emptyList<DoItAgainEntity>()
internal fun setActivities(activities: List<DoItAgainEntity>) {
this.activities = activities
notifyDataSetChanged()
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ActivitiesViewHolder =
ActivitiesViewHolder(
LayoutInflater.from(parent.context).inflate(
R.layout.recyclerview_item,
parent,
false
)
)
override fun onBindViewHolder(holder: ActivitiesViewHolder, position: Int) {
holder.bind(activities[position])
}
override fun getItemCount() = activities.size
}
class ActivitiesViewHolder(
override val containerView: View
) : RecyclerView.ViewHolder(containerView), LayoutContainer {
fun bind(vo: DoItAgainEntity) {
itemView.textView.text = vo.engagement
}
}
The Fragment code:
class ShowDBEntriesFragment : Fragment() {
private lateinit var viewModel: ShowDBEntriesViewModel
private lateinit var activitiesAdapter: ActivitiesAdapter
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View = inflater.inflate(R.layout.show_dbentries_fragment, container, false)
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
viewModel = ViewModelProviders.of(this).get(ShowDBEntriesViewModel::class.java)
activitiesAdapter = ActivitiesAdapter()
recyclerview.apply {
addItemDecoration(
DividerItemDecoration(
context!!,
LinearLayoutManager.VERTICAL
)
)
layoutManager = LinearLayoutManager(context!!, LinearLayoutManager.VERTICAL, false)
adapter = activitiesAdapter
}
// for testing purposes. could be deleted easily
activitiesAdapter.setActivities(
listOf(
DoItAgainEntity(1, "play guitar", 100),
DoItAgainEntity(2, "make breakfast", 2),
DoItAgainEntity(2, "go out with friends", 20)
)
)
fab.setOnClickListener {
val fragmentManager = (activity as MainActivity).supportFragmentManager
val fragmentTransaction = fragmentManager.beginTransaction()
val fragment = InsertNewEngagementFragment()
fragmentTransaction.replace(R.id.fragment_newEngagement, fragment)
fragmentTransaction.commit()
}
}
}
The xml code from recyclerview_item.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="vertical">
<TextView
android:id="#+id/textView"
style="#style/activity_title"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="#android:color/holo_orange_light" />
</LinearLayout>
I'm trying to learn how to implement databinding in an Android app. I have a small app I'm working with to learn this. And while I have databinding working for part of the app. I have hit a hiccup when trying to implement a recyclerview. I just cannot seem to get it. Been banging away at it for two or three days, and getting frustrated. Thought I'd ask you guys.
The app is super simple at this point.
The part i'm stuck on is accessing my recyclerview from an .xml layout from my MainFragment.kt
At first I was trying to use binding, but got frustrated and went back to just trying to use findViewById, but that is giving me issue too. I am beginning to think, I don't have as firm a grasp on databinding as I thought I did.
This is from the fragment that holds the recyclerView:
fragment_main.xml
<androidx.recyclerview.widget.RecyclerView
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_marginTop="8dp"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintStart_toStartOf="parent"
android:layout_marginStart="8dp"
android:layout_marginBottom="8dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
android:layout_marginEnd="8dp"
android:id="#+id/job_recyclerView"/>
I have another small layout file that is using Cardview to show each individual item in the recyclerview
A super simple Model:
JobData.kt
data class JobData(val companyName: String, val location: String)
An Adapter:
JobAdapter.kt
class CustomAdapter(val userList: ArrayList<JobData>) : RecyclerView.Adapter<CustomAdapter.ViewHolder>() {
//Returning view for each item in the list
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): CustomAdapter.ViewHolder {
val v = LayoutInflater.from(parent.context).inflate(R.layout.job_item_layout, parent, false)
return ViewHolder(v)
}
//Binding the data on the list
override fun onBindViewHolder(holder: CustomAdapter.ViewHolder, position: Int) {
holder.bindItems(userList[position])
}
override fun getItemCount(): Int {
return userList.size
}
//Class holds the job list view
class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
fun bindItems(job: JobData) {
val textViewName = itemView.findViewById(R.id.tv_company_name) as TextView
val textViewAddress = itemView.findViewById(R.id.tv_Location) as TextView
textViewName.text = job.companyName
textViewAddress.text = job.location
}
}
}
And then the code in my MainFragment to handle it all, which it is not doing. I've tried everything, it was getting ugly. As you can see below. Binding is in place and working for my FloatingActionButton. But I for some reason cannot figure out how to access that recylerview. At the point the code is at below, I thought I'd just accessing using findViewById, but that is not working either.
MainFragment.kt
class MainFragment : Fragment() {
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
val binding: FragmentMainBinding = DataBindingUtil.inflate(
inflater, R.layout.fragment_main, container, false)
//Setting onClickListener for FAB(floating action button) using Navigation
binding.createNewJobFAB.setOnClickListener { v: View ->
v.findNavController().navigate(R.id.action_mainFragment_to_createNewJobFragment)
}
//getting recyclerview from xml
val recyclerView = findViewById(R.id.job_recyclerView) as RecyclerView
//adding a layoutmanager
recyclerView.layoutManager = LinearLayoutManager(this, RecyclerView.VERTICAL, false)
//Arraylist to store jobs using the data class JobData
val jobs = ArrayList<JobData>()
//add dummy data to list
jobs.add(JobData("A Company", "Town A"))
jobs.add(JobData("B Company", "Town B"))
jobs.add(JobData("C Company", "Town C"))
jobs.add(JobData("D Company", "Town D"))
//creating adapter
val adapter = CustomAdapter(jobs)
//add adapter to recyclerView
recyclerView.adapter = adapter
return binding.root
}
}
The above fails to compile for two reasons:
findViewById shows as an "Unresolved Reference".
When adding the layoutManager, "this" shows as a "Type Mismatch"
Which I believe is due to the fact that Fragments do not have a context. Or so, I think anyway. But I don't know to resolve that? Maybe override some other method, but I can't seem to figure out which or how?
Oh and MainActivity looks like:
MainActivity.kt
class MainActivity : AppCompatActivity() {
//private lateinit var binding: ActivityMainBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
#Suppress("UNUSED_VARIABLE")
val binding = DataBindingUtil.setContentView<ActivityMainBinding>(this, R.layout.activity_main)
}
//Ensures back button works as it should
override fun onSupportNavigateUp() = findNavController(this, R.id.navHostFragment).navigateUp()
}
Which is pointing to Nav_Graph for Android Navigation (part of JetPack). This bit is fine and working.
Adding gradle files to show that my dependencies were set correctly as suggested below.
app/gradle
android {
compileSdkVersion 28
dataBinding {
enabled = true
}
...
}
kapt {
generateStubs = true
correctErrorTypes = true
}
dependencies {
...
kapt "com.android.databinding:compiler:$gradle_version"
...
}
Encase your xml in <layout>..<layout/>
private lateinit var binding: FragmentXXXBinding
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
binding = FragmentXXXBinding.inflate(inflater)
return binding.root
}
Then you can call recyclerview by binding.jobRecyclerview
try to set all the click listeners etc on onViewCreated rather than onCreateView of fragment
It is wrong way to findViewById from Fragment(it is good technique for Activity):
val recyclerView = findViewById(R.id.job_recyclerView) as RecyclerView
First, fragment's layout have to be return by onCreateView() method.
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
return inflater.inflate(R.layout.fragment_main, container, false)
}
I personally like do all fragment's business logic inside onViewCreated()
override fun onViewCreated(view: View?, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
//Now, we can use views by kotlinx
//val recyclerView = job_recyclerView
//Or old-fashioned way
val recyclerView = getView()!!.findViewById(R.id.job_recyclerView) as RecyclerView
}
RecylerView can be accessed from fragment's layout by having root view like: getView()!!.findViewById or by kotlinx inside onViewCreated(): job_recyclerView
Ok, so first of all you are getting error on findViewById because your fragment is unaware about the view that contains recyclerView
What you should do is, take an instance of view that you are inflating for this fragment (declare view as a global variable, replace your inflater line with this).
var rootView
// Inside onCreateView
var rootView = inflater?.inflate(R.layout.fragment, container, false)
Now replace, findViewById() with rootView.findViewById()
And the other error is because the fragment does not have any context of it's own so replace this with activity!!
By writing activity!! you are calling getActicity() method which returns context of parent activity.