Calling method from Fragment in RecyclerView Adapter - Lateinit property textView has not been initialized - kotlin

I have a list of items. When I swipe on an item, I want to update a textView in the Fragment.
I use a RecyclerView. In the Adapter, I detect when a user swipes something, and then I try to call a method in the Fragment to update a textview.
However, when I try it crashes with "LateInit Propery TextView has not been initialized". I have tried initializing the textView in the adapter instead, but I don't think that's correct and it seems impossible as findViewById doesn't really work in an adapter since the specific TextView is not on an item layout, but instead displayed in the Fragment Layout (and it needs to be that way as I don't want the textview on every single item).
My adapter (simplified)
class SwipeAdapter internal constructor(private val mCandidatesArrayList: ArrayList<SwipeCandidate>) :
RecyclerView.Adapter<RecyclerView.ViewHolder>() {
private val mSwipeFragment = SwipeFragment() //Here I initialize my fragment
fun createHelperCallback(): ItemTouchHelper.Callback {
return object : ItemTouchHelper.SimpleCallback(0, ItemTouchHelper.RIGHT) {
override fun onMove(
recyclerView: RecyclerView, viewHolder: RecyclerView.ViewHolder,
target: RecyclerView.ViewHolder
): Boolean {
return false
}
override fun onSwiped(viewHolder: RecyclerView.ViewHolder, swipeDir: Int) {
val position = viewHolder.bindingAdapterPosition
mSwipeFragment.updateTextView() //This throws the error
}
}
}
}
}
And my Fragment (simplified):
class SwipeFragment : Fragment() {
private lateinit var textView : TextView
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
textView = root.findViewById(R.id.textview) //initializing textview
}
fun updateTextView() {
textView.setText("Hello") //Setting the text
}
}

You are trying to get view from a fragment that is not open. In our fragment, as I understand it, the adapter is initialize, you need to pass a listener into it, which will be called after the swipe.
class SwipeAdapter internal constructor(var onSwiped: (position: Int) -> Unit = { }, private val mCandidatesArrayList: ArrayList<String>) : RecyclerView.Adapter<RecyclerView.ViewHolder>() {
fun createHelperCallback(): ItemTouchHelper.Callback {
return object : ItemTouchHelper.SimpleCallback(0, ItemTouchHelper.RIGHT) {
override fun onMove(
recyclerView: RecyclerView, viewHolder: RecyclerView.ViewHolder,
target: RecyclerView.ViewHolder,
): Boolean {
return false
}
override fun onSwiped(viewHolder: RecyclerView.ViewHolder, swipeDir: Int) {
val position = viewHolder.bindingAdapterPosition
onSwiped(position)
}
}
}
}
fragment
class SwipeFragment : Fragment() {
private lateinit var textView: TextView
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?,
): View {
textView = root.findViewById(R.id.textview)
val adapter = SwipeAdapter({ position: Int ->
textView.setText("Hello")
}, arrayListOf())
}
}

Related

how can i add counter to viewpager2

image
I couldn't find how to do the counter algorithm
here is the adapter i wrote for my viewpager
class ViewPagerAdapter(val images : List,val mContext: Context) :
RecyclerView.Adapter<ViewPagerAdapter.ViewPagerViewHolderClass>() {
inner class ViewPagerViewHolderClass(view: View) : RecyclerView.ViewHolder(view){
val viewPagerFoto : ImageView
init {
viewPagerFoto = view.findViewById(R.id.viewPagerFoto)
}
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewPagerViewHolderClass {
val view = LayoutInflater.from(mContext).inflate(R.layout.item_view_pager,parent,false)
return ViewPagerViewHolderClass(view)
}
override fun onBindViewHolder(holder: ViewPagerViewHolderClass, position: Int) {
val image = images[position]
holder.viewPagerFoto.setImageResource(image)
}
override fun getItemCount(): Int {
return images.size
}
}
My home Fragment
I am performing operations on homefragment
class HomeFragment : Fragment() {
private lateinit var _binding : FragmentHomeBinding
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
_binding = FragmentHomeBinding.inflate(inflater,container,false)
val images = listOf(
R.drawable.f1,
R.drawable.f2,
R.drawable.f3,
R.drawable.f4,
R.drawable.f5,
R.drawable.f6,
)
val adapter by lazy { ViewPagerAdapter(images,requireContext()) }
_binding.viewPager.adapter = adapter
return _binding.root
}
}
override fun onBindViewHolder(holder: ViewPagerViewHolderClass, position: Int) {
val image = images[position]
holder.viewPagerFoto.setImageResource(image)
}
You can use position parameter, if you want just use it on there.
But i suggest that use it on ViewPagerViewHolderClass with declaring new parameter named position and pass it over there.
In onBindViewHolder method you have position parameter. You can pass it to viewHolder and use it

RecyclerView adapter doesnt execute any methods

I am working on a simple project. I am trying to show list of data but in adapter none of the methods are called
class PlacnikiAdapter(private val placniki: List<Placnik>) :
RecyclerView.Adapter<PlacnikiAdapter.ViewHolder>() {
class ViewHolder(view: View) : RecyclerView.ViewHolder(view) {
val textView: TextView
init {
// Define click listener for the ViewHolder's View.
textView = view.findViewById(R.id.ime)
}
}
override fun onCreateViewHolder(viewGroup: ViewGroup, viewType: Int): ViewHolder {
val inflater = LayoutInflater.from(viewGroup.context).inflate(R.layout.layout_rv_item,
viewGroup, false)
return ViewHolder(inflater)
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
holder.textView.text = placniki[position].ime
}
override fun getItemCount(): Int {
return placniki.size
}
class PlacnikiFragment : Fragment() {
private val TAG = "PlacnikiFragment"
private lateinit var binding: PlacnikiFragmentBinding
lateinit var viewModel: MainViewModel
private val retrofitService = RetrofitService.getInstance()
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
// Defines the xml file for the fragment
return inflater.inflate(R.layout.placniki_fragment, container, false)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
binding = PlacnikiFragmentBinding.inflate(layoutInflater)
//get viewmodel instance using MyViewModelFactory
viewModel =
ViewModelProvider(this, ViewModelFactory(Repository(retrofitService))).get(
MainViewModel::class.java
)
//set recyclerview adapter
viewModel.placnikiList.observe(viewLifecycleOwner, {
Log.d(TAG, "placnikiList: $it")
binding.recyclerview.adapter = PlacnikiAdapter(it)
})
viewModel.errorMessage.observe(viewLifecycleOwner, {
Log.d(TAG, "errorMessage: $it")
})
viewModel.getVsiPlacniki()
}
I dont know what could be causing this. I changed activity to a fragment and beforehand everything worked normally and after changing to fragment recyclerview isnt showing items and the list isnt empty either so i dont pass list of zero items
I found the solution, the problem was in data binding. I had to add below code in gradle module
buildFeatures{dataBinding = true}

RecyclerView with coroutines, using Fragment doesn't populate data Kotlin

I am trying to work wih courutines and recycler view. I've done everything necessary to send requests to the API, but I still can't get the list that should be inside the recycler view. I am using fragment to create list and bottom nav. When I go to the fragment where my list should be, then I get the error: RecyclerView: No layout manager attached; skipping layoutand nothing more. I googled about this error and it says I should define the layout manager in xml, but it alreadt has layuout manager in my fragment
<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_item"
app:layoutManager="LinearLayoutManager"
Also my adapter and fragment for recycler view look like this:
class MyItemRecyclerViewAdapter(
) : RecyclerView.Adapter<MyItemRecyclerViewAdapter.ViewHolder>() {
var userNameResponse = mutableListOf<UsersNameItem>()
fun setNamesList(names: List<UsersNameItem>) {
this.userNameResponse = userNameResponse.toMutableList()
notifyDataSetChanged()
}
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 = userNameResponse[position]
holder.idView.text = item.id
holder.contentView.text = item.name
}
override fun getItemCount(): Int = userNameResponse.size
class ViewHolder(binding: FragmentItemBinding) : RecyclerView.ViewHolder(binding.root) {
val idView: TextView = binding.itemNumber
val contentView: TextView = binding.content
}
}
Fragment:
class ItemFragment : Fragment() {
private var layoutManager: RecyclerView.LayoutManager? = null
private var adapter: RecyclerView.Adapter<MyItemRecyclerViewAdapter.ViewHolder>? = null
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
return inflater.inflate(R.layout.fragment_item_list, container, false)
}
override fun onViewCreated(itemView: View, savedInstanceState: Bundle?) {
super.onViewCreated(itemView, savedInstanceState)
layoutInflater.apply {
layoutManager = LinearLayoutManager(activity)
adapter = MyItemRecyclerViewAdapter()
}
}
companion object {
fun newInstance() = ItemFragment()
}
}
And MainActivity where I set up courutines and trying to make a request:
class MainActivity : AppCompatActivity() {
private lateinit var binding: ActivityMainBinding
lateinit var nameviewModel: ListNamesViewModel
val adapter = MyItemRecyclerViewAdapter()
///trying to create http-request for lists
val retrofitService = BasicApiService.getInstance()
val mainRepository = MainRepository(retrofitService)
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
nameviewModel = ViewModelProvider(
this,
MyViewModelFactory(mainRepository)
)[ListNamesViewModel::class.java]
nameviewModel.userName.observe(
this, Observer {
adapter.setNamesList(it)
})
nameviewModel.message.observe(this) {
Toast.makeText(this, it, Toast.LENGTH_SHORT).show()
}
nameviewModel.loadUsers()
val navView: BottomNavigationView = binding.navView
val navController = findNavController(R.id.nav_host_fragment_activity_main)
val appBarConfiguration = AppBarConfiguration(
setOf(
R.id.navigation_home,
R.id.navigation_dashboard,
R.id.navigation_notifications,
R.id.list_name_navig
)
)
setupActionBarWithNavController(navController, appBarConfiguration)
navView.setupWithNavController(navController)
}
}
Also I wanted to ask (I am very new to Kotlin nad just trying to make things work) if it is a good practice to leave request to API in the MainActivity ot I should do it in another way?
I see some confusion in your code, try to apply the next:
Why are you creating the layoutManager and adapter in the ItemFragment?
override fun onViewCreated(itemView: View, savedInstanceState: Bundle?) {
super.onViewCreated(itemView, savedInstanceState)
layoutInflater.apply {
layoutManager = LinearLayoutManager(activity)
adapter = MyItemRecyclerViewAdapter()
}
}
You need to move it and RecyclerView to your activity.
The using layoutInflater.apply {} doesn't make sense, it's doing nothing in your case. You need to set layoutManager and adapter to recyclerView then in the activity it should look like that (and rename list_item to recyclerView)
binding.recyclerView.apply {
layoutManager = LinearLayoutManager(this)
adapter = MyItemRecyclerViewAdapter()
}
Remove app:layoutManager="LinearLayoutManager" from XML.
It looks like you don't need to use ItemFragment for your purpose because you use it just like a view and binding in the ViewHolder
class ViewHolder(binding: FragmentItemBinding) : RecyclerView.ViewHolder(binding.root) {
val idView: TextView = binding.itemNumber
val contentView: TextView = binding.content
}
try changing in xml
app:layoutManager="LinearLayoutManager"
to
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"

How to pass data from a BottomSheetFragment to another fragment?

I have an Activity, called 'HomeActivity', where I have a bottom navigation menu and four fragments. The activity has a FloatingActionButton and when I hit this, it opens a bottomSheetFragment which contain a RecyclerView and when I hit an item from it, I want some values to send to the fragment ('HomeFragment') which is part of the 'HomeActivity'
After doing a lot of searches online, I have done the following but I am not able to get the data in the fragment.
Following is the fragment from which I want to send the data.
'HomeBottomSheetFragment.kt'
class HomeBottomSheetFragment: BottomSheetDialogFragment() {
private lateinit var binding: FragmentHomeBottomSheetBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
FirestoreClass().getCategoryList(this)
}
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?
): View {
binding = FragmentHomeBottomSheetBinding.inflate(inflater, container, false)
return binding.root
}
fun successCategoryList(categoryList: ArrayList<ProdCategories>) {
binding.rvHomeCategories.visibility = View.VISIBLE
binding.rvHomeCategories.layoutManager = GridLayoutManager(context, 4)
binding.rvHomeCategories.setHasFixedSize(true)
val categoryAdapter = CategoryListAdapter(requireContext(), categoryList)
binding.rvHomeCategories.adapter = categoryAdapter
categoryAdapter.setOnClickListener(object :CategoryListAdapter.OnClickListener{
override fun onClick(position: Int, category: ProdCategories) {
val myFragment = HomeFragment()
val bundle = Bundle()
bundle.putString("category", category.category_name)
myFragment.arguments = bundle
fragmentManager?.beginTransaction()?.replace(R.id.nav_host_fragment,HomeFragment())?.commit()
}
})
}
}
Following is the adapter of the above fragment ('HomeBottomSheetFragment.kt'), 'CategoryListAdapter.kt'.
open class CategoryListAdapter(private val context: Context, private var list: ArrayList<ProdCategories>)
: RecyclerView.Adapter<RecyclerView.ViewHolder>() {
private var onClickListener: OnClickListener? = null
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
return MyViewHolder(
HomeBottomsheetCategoryListLayoutBinding.inflate(
LayoutInflater.from(
parent.context
), parent, false
)
)
}
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
val model = list[position]
if (holder is MyViewHolder) {
GlideLoader(context).loadProductPicture(model.category_image, holder.binding.ivCategoryImage)
holder.binding.tvCategoryName.text=model.category_name
}
holder.itemView.setOnClickListener {
if (onClickListener != null) {
onClickListener!!.onClick(position, model)
}
}
}
override fun getItemCount(): Int {
return list.size
}
fun setOnClickListener(onClickListener: OnClickListener) {
this.onClickListener = onClickListener
}
interface OnClickListener{
fun onClick(position: Int, category: ProdCategories)
}
private class MyViewHolder(val binding: HomeBottomsheetCategoryListLayoutBinding) : RecyclerView.ViewHolder(binding.root)
}
Following is the fragment where I want to receive the data.
class HomeFragment : BaseFragment() {
private var filterCategory: String?= null
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?
): View? {
binding = FragmentHomeBinding.inflate(inflater, container, false)
val bundle = this.arguments
if (bundle!=null) {
filterCategory = bundle.getString("category")
}
return binding.root
}
}
All I know is that I am not doing things right, please help me fix it.
In this line,
fragmentManager?.beginTransaction()?.replace(R.id.nav_host_fragment,HomeFragment())?.commit()
Replace HomeFragment() with myFragment because that's where you are attaching the bundle.
See if this solves your problem.

Recycler View and DialogFragment struggling (Kotlin)

Hello i am using a recyclerView to swipe and call for a Dialog fragment
The the idea is that when i dismiss the dialog Fragment the raw come back to the original place
but the problem is that it return, just after the dialog is opened.
So i do not how to call notifyItemChanged inside the dialogFragment, or implement the dialogFragment.setOnDismissListener because when i override i dont know how to pass to the function the adapter to call notifyItemChanged i
This is my code
class Fragmento : Fragment(), RecyclerAdapter.ClickListener {
// TODO: Rename and change types of parameters
private lateinit var adapter :RecyclerAdapter
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
arguments?.let {
}
}
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?): View? {
// Inflate the layout for this fragment
val view =inflater.inflate(R.layout.fragment_fragmento, container,false)
initRecyclerView(view)
return view
}
private fun initRecyclerView(view: View) {
val recyclerView = view.findViewById<RecyclerView>(R.id.recyclerView)
recyclerView.layoutManager=LinearLayoutManager(activity)
adapter = RecyclerAdapter()
recyclerView.adapter=adapter
val itemSwipe=object:ItemTouchHelper.SimpleCallback(0,ItemTouchHelper.LEFT){
override fun onMove(
recyclerView: RecyclerView,
viewHolder: RecyclerView.ViewHolder,
target: RecyclerView.ViewHolder
): Boolean {
return false
}
override fun onSwiped(viewHolder: RecyclerView.ViewHolder, direction: Int) {
showDialog(viewHolder as RecyclerAdapter.ViewHolder)
var dialog = DialogFragment()
dialog.show(childFragmentManager,"dialog")
adapter.notifyItemChanged(viewHolder.adapterPosition)
}
}
val swap =ItemTouchHelper(itemSwipe)
swap.attachToRecyclerView(recyclerView)
}
private fun showDialog(viewHolder: RecyclerAdapter.ViewHolder){
val builder= AlertDialog.Builder(activity)
builder.setTitle("DeleteItem")
}
companion object {
/**
* Use this factory method to create a new instance of
* this fragment using the provided parameters.
*
*/
// TODO: Rename and change types and number of parameters
#JvmStatic
fun newInstance() =
Fragmento().apply {
arguments = Bundle().apply {
}
}
}
}
and for the DialogFragment
class DialogFragment: DialogFragment(){
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
var rootView: View = inflater.inflate(R.layout.dialog_fragment_noticias, container, false)
return rootView
}
override fun onDismiss(dialog: DialogInterface) {
super.onDismiss(dialog)
}
}
If I have understand the problem here is that you want to call notifyItemChanged() when your dialog is dismiss without setOnDismissListener.
If you want to do that you can simply add a callback on your DialogFragment and use it in your recyclerView.
class DialogFragment(dismiss: () -> Unit): DialogFragment(){
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
var rootView: View = inflater.inflate(R.layout.dialog_fragment_noticias, container, false)
return rootView
}
override fun onDismiss(dialog: DialogInterface) {
super.onDismiss(dialog)
dismiss()
}
}
When you are creating the DialogFragment you can use the callback :
override fun onSwiped(viewHolder: RecyclerView.ViewHolder, direction: Int) {
showDialog(viewHolder as RecyclerAdapter.ViewHolder)
var dialog = DialogFragment() {
//here you are notified when the dialog is dismiss
adapter.notifyItemChanged(viewHolder.adapterPosition)
}
dialog.show(childFragmentManager,"dialog")
adapter.notifyItemChanged(viewHolder.adapterPosition)
}