how to navigate to fragment inside recycler view? - kotlin

I have an activity that is controlled with a navigation component, it has few fragments, inside one of these fragments there is a recyclerView that has some items, when I click on an Item I want it to navigate me to another fragment that has additional information about the item, I don't know how to use navigation component inside a recycelerView, when I type findNavController it has some parameters that am not sure what to put in or if its even the right function, I also tried to do it by code like this:
val fm = (context as AppCompatActivity).supportFragmentManager
fm.beginTransaction()
.replace(R.id.fragmentContainer, fragment)
.addToBackStack(null)
.commit()
by the way this is the code that asks for other parameters:
// it asks for a (fragment) or (activity, Int)
findNavController().navigate(R.id.action_settingsFragment_to_groupUnits)
the problem is when I navigate out of this fragment or use the drawer navigation (nav component for the other fragments), this fragment that I navigated to stays displayed in the screen, I see both fragments at the same time, I assume its a fragment backStack issue but I don't know how to solve it, thanks for the help and your time in advance

You do not need to navigate from RecyclerView item click to AdditionalDetails fragment directly.
You can do this same thing by help of interface.
Steps:
Create an interface with a method declaration.
Extend Interface from the fragment where you are using your RecyclerView and Implement interface method.
Pass this interface with the adapter.
Using the interface from adapter you just pass object when click on item.
Finally from your fragment you just navigate to AdditionalDetails fragment with argument.
Lets see sample code from my current project:
Interface
interface ChatListClickListener {
fun onChatListItemClick(view:View, user: User)
}
Adapter Class
class UserAdapter(val Users: List<User>, val chatListClickListener: ChatListClickListener) : RecyclerView.Adapter<UserAdapter.UserViewHolder>() {
inner class UserViewHolder(
val recyclerviewUsersBinding: RecyclerviewChatlistBinding
) : RecyclerView.ViewHolder(recyclerviewUsersBinding.root)
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): UserViewHolder {
val vh = UserViewHolder(
DataBindingUtil.inflate(
LayoutInflater.from(parent.context),
R.layout.recyclerview_chatlist,
parent,
false
)
)
return vh
}
override fun onBindViewHolder(holder: UserViewHolder, position: Int) {
holder.recyclerviewUsersBinding.user = Users[position]
holder.recyclerviewUsersBinding.root.setOnClickListener{
chatListClickListener.onChatListItemClick(it,Users[position])
}
}
override fun getItemCount(): Int {
return Users.size
}
}
My fragment
class FragmentChatList : Fragment(), ChatListClickListener {
lateinit var binding: FragmentChatListBinding
lateinit var viewModel: ChatListViewModel
lateinit var listener: ChatListClickListener
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
}
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
val args: FragmentChatListArgs by navArgs()
binding = FragmentChatListBinding.inflate(layoutInflater, container, false)
val factory = ChatListFactory(args.user)
viewModel = ViewModelProvider(this, factory).get(ChatListViewModel::class.java)
binding.viewModel = viewModel
listener = this
lifecycleScope.launch {
viewModel.addUserWhenUserConnect()
}
viewModel.userList.observe(viewLifecycleOwner, Observer { data ->
binding.rvChatList.apply {
layoutManager = LinearLayoutManager(requireContext())
setHasFixedSize(true)
adapter = UserAdapter(data, listener)
}
})
return binding.root
}
override fun onChatListItemClick(view: View, user: User) {
Toast.makeText(requireContext(), user.name + "", Toast.LENGTH_SHORT).show()
// here you navigate to your fragment....
}
}
I guess this will be helpful.

Related

How can I change recyclerview item background color permanent and transact new Fragment?

I want to change the color of recyclervView item that I clicked(so user can understand that already checked the detail of the item) and go detail fragment page. However this background color change must be permanent should I store it in livedata of recycler view items. I share my codes at the end I am new to android programming so please explain your solution for beginner and my english level is not good. Thanks for everything.
class AdapterRecycler() :
RecyclerView.Adapter<AdapterRecycler.ViewHolder>()
class ViewHolder(view: View, listener: onItemClickListener) : RecyclerView.ViewHolder(view) {
val name: TextView = view.findViewById(R.id.gameId)
val score: TextView = view.findViewById(R.id.scoreId)
val genre: TextView = view.findViewById(R.id.genres)
val layout1: RelativeLayout = view.findViewById(R.id.rowlayout)
init {
// Define click listener for the ViewHolder's View.
//val textView : TextView = view.findViewById(R.id.gameId)
itemView.setOnClickListener {
layout1.setBackgroundColor(Color.rgb(224,224,224))
listener.onItemClick(adapterPosition)
}
}
}
// Create new views (invoked by the layout manager)
override fun onCreateViewHolder(viewGroup: ViewGroup, viewType: Int): ViewHolder {
// Create a new view, which defines the UI of the list item
val view = LayoutInflater.from(viewGroup.context)
.inflate(R.layout.text_row_item, viewGroup, false)
return ViewHolder(view, listenerItems)
}
class Games : Fragment() {
...
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
...
adapter.setOnItemClickListener(object : AdapterRecycler.onItemClickListener {
override fun onItemClick(position: Int) {
// Fragment transaction to detail page
requireActivity().supportFragmentManager.beginTransaction()
.replace(R.id.fragmentContainerView2, Details()).commit()
}
})
}
Should I store a boolean value in here to save item checked status?
data class Game(val name : String, val score : Int, val genres : Array<String>)
I tried solution at my codes and I get transaction but not the color changes of item layout.
I think it will be good if you'll update your Game model, after it became checked. Something like this:
data class Game(.., var isChecked: Boolean = false)
And inside your AdapterRecycler.onItemClickListener realization call ViewModel function to update isChecked value:
fun onItemChecked(position: Int) {
val item = TODO("get item by position from your live data")
item.isChecked = !item.isChecked
}
In addition, you should override your ViewHolder to setup a background rely on your isCheked value (it will be the same as must be after ViewHolder notify calls):
class ViewHolder(view: View, listener: onItemClickListener) : RecyclerView.ViewHolder(view) {
...
fun bind(item: Game) {
val backColor = if (item.isChecked) Color.GREEN else Color.BLUE
layout1.setBackgroundColor(Color.rgb(224,224,224))
}
}
Call bind function from your AdapterRecycler.onBindViewHolder this way:
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
holder.bind(getItem(position), callback)
}
Don't forget to notify your adapter.
Guys thanks to your replies I reviewed my code and got solution.
Here is my edit at the code:
override fun onBindViewHolder(viewHolder: ViewHolder, position: Int) {
...
val color = if (_data[position].isChecked) Color.rgb(224,224,224) else Color.WHITE
viewHolder.layout1.setBackgroundColor(color)
...
}
That way I change the color of recyclerview item color since I change the fragments make the viewmodel at activity based and used it shared viewModel between fragments
class MainActivity : AppCompatActivity() {
private lateinit var viewModel: GamesViewModel
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
...
viewModel = ViewModelProvider(this)[GamesViewModel::class.java]
...
}
Then I added the boolean variable to my model.
data class Game(val name : String, val score : Int, val genres : Array<String>, var isChecked : Boolean = false)
Then I change the liveData's attribute of isChecked at the click listener.
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
adapter.setOnItemClickListener(object : AdapterRecycler.onItemClickListener {
override fun onItemClick(position: Int) {
viewModel= ViewModelProvider(requireActivity()).get(GamesViewModel::class.java)
val item = viewModel.liveData.value!!.get(position)
item.isChecked = true
// toGo next fragment
requireActivity().supportFragmentManager.beginTransaction()
.replace(R.id.fragmentContainerView2, Details()).commit()
}
})
VoilĂ  we got the perfect result thanks for helping me, have a great day.

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}

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

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())
}
}

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)
}

Kotlin OnItemClickListener for a RecyclerView inside a Fragment

The problem is related to the fact that I don't manage to implement a OnItemClick listener for a RecyclerView that is located in a "mainFragment" by implementing the OnClickListener in the Adapter.
I would like my application (kotlin) to launch another fragment ("deletePage" in the code bellow) everytime an itemView (an ImageView) from the RecyclerView is clicked, this fragment would display the same photo in big.
My Adapter code is the following one:
class MyAdapter : RecyclerView.Adapter<MyAdapter.MyViewHolder>() {
private var photo = emptyList<Photo>()
inner class MyViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView)
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MyViewHolder {
return MyViewHolder(
LayoutInflater.from(parent.context).inflate(R.layout.row_layout, parent, false)
)
}
override fun getItemCount(): Int {
return photo.size
}
override fun onBindViewHolder(holder: MyViewHolder, position: Int) {
holder.itemView.longitude.text = photo[position].latitude
holder.itemView.latitude.text = photo[position].longitude
holder.itemView.imageView.load(photo[position].photo)
}
fun setData(photo: List<Photo>) {
this.photo = photo
notifyDataSetChanged()
}
}
And my mainFragment code is the following one:
class MainFragment : Fragment(){
private lateinit var myView: MyViewModel
private val adapter by lazy { MyAdapter() }
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
// Inflate the layout for this fragment
val view = inflater.inflate(R.layout.fragment_main, container, false)
//Recyclerview
val adapter = MyAdapter()
val recyclerView = view.recycler_view
recyclerView.adapter = adapter
recyclerView.layoutManager = LinearLayoutManager(requireContext())
myView = ViewModelProvider(this).get(MyViewModel::class.java)
myView.readPhoto.observe(viewLifecycleOwner, Observer {photo ->
adapter.setData(photo)
})
setHasOptionsMenu(true)
return view
}
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
inflater.inflate(R.menu.menu_database, menu)
}
override fun onOptionsItemSelected(item: MenuItem): Boolean {
when {
item.itemId == R.id.deleteAll -> findNavController().navigate(R.id.deletePage)
item.itemId == R.id.refresh -> {
Toast.makeText(activity, "yep", Toast.LENGTH_SHORT).show()
instertDataToDatabase()
}
}
if (item.itemId == R.id.deleteAll) {
}
return super.onOptionsItemSelected(item)
}
}
The objective is that when you click on an item of the RecyclerView the "findNavController().navigate(R.id.deletePage)" fragment is displayed but everytime I try to implement a solution the application crashes when clicking at an item of the RecyclerView. Right now the navigation works by the click on a button in the Menu at the toolbar but is not the ideal solution.
Any sort of help or advice would be very much appreciated!
Firstly you need to give click listener of your itemView in onBindViewHolder
override fun onBindViewHolder(holder: MyViewHolder, position: Int) {
holder.itemView.longitude.text = photo[position].latitude
holder.itemView.latitude.text = photo[position].longitude
holder.itemView.imageView.load(photo[position].photo)
holder.itemView.setOnClickListener { navigateToFragment() }
}
For the navigation somehow you should have a context instance.
Here is the possible solutions that came up to my mind:
Give navigator instance to adapter
class Navigator(fragment: Fragment) {
private val fragmentRef = WeakReference(fragment)
fun navigate(){
// make your navigations here with using fragmentRef.get()
}
}
In your fragment:
private val navigator = Navigator(this)
...
val adapter = MyAdapter(navigator)
In your adapter:
class MyAdapter(private val navigator: Navigator) : RecyclerView.Adapter<MyAdapter.MyViewHolder>() {
fun navigateToFragment(){
navigator.navigate()
}
Make interface for callback
2.1. Implement that interface in fragment and give adapter the interface instance
interface Navigable{
fun navigate()
}
In your fragment:
class MainFragment : Fragment(), Navigable{
...
override fun navigate(){
// make your navigation
}
val adapter = MyAdapter(navigator)
In your adapter:
class MyAdapter(private val navigable: Navigable) : RecyclerView.Adapter<MyAdapter.MyViewHolder>() {
fun navigateToFragment(){
navigable.navigate()
}
2.2. Make the interface, but do not pass reference, use findFragment in adapter
In your adapter: override onAttachedToRecyclerView to find fragment
private lateinit var navigable: Navigable
override fun onAttachedToRecyclerView(recyclerView: RecyclerView) {
super.onAttachedToRecyclerView(recyclerView)
navigable = recyclerView.findFragment<Fragment>() as Navigable
}
fun navigateToFragment(){
navigable.navigate()
}