I am using Bottom Navigation Activity in my project. Which contains following activities. I am using kotlin Language. I am not sure how to route from one fragment to another fragment in bottom navigation activity. Any help is appreciated.
Code:
https://github.com/joshvapeter/KotlinBottomNavigationNew
Activies
1)One Main Activity
2)Two Fragments
3)Two Adapter Class
4)Two Layout files
Expectation
When I click the Fragment One Recycler View Item then It need to automatically route it to Fragment two Recycler View Item in the next bottom tab. Below is the code, I am using in my project.
Code
Fragment One Adapter
itemView.setOnClickListener {
Log.d("Fragment One Clicked","Fragment One Clicked")
//FragmentTwo()
}
MainActivity
fun ShowFragmentOne() {
val transaction = manager.beginTransaction()
val fragment = FragmentOne()
transaction.replace(R.id.one, fragment)
transaction.addToBackStack(null)
transaction.commit()
isFragmentOneLoaded = true
}
fun ShowFragmentTwo() {
val transaction = manager.beginTransaction()
val fragment = FragmentTwo()
transaction.replace(R.id.two, fragment)
transaction.addToBackStack(null)
transaction.commit()
isFragmentOneLoaded = false
}
I don't have enough reputation to add a comment. So on the basis of my understanding, i am writing this answer. If my understanding is wrong, please comment.
From what i can understand you want to move to FragmentTwo when an item is clicked in RecyclerView in FragmentOne. You can achieve it in following way:
FragmentOne:
fun onItemSelected(item:MyModel){
(activity as MainActivity).showFragmentTwo(item)
}
FragmentOneAdapter:
class FragmentOneAdapter(val fragment:FragmentOne,val myList:ArrayList<MyModel>):RecyclerView.Adapter<MyViewHolder>(){
override fun onCreateViewHolder(p0: ViewGroup, p1: Int): MyViewHolder {
//your code to create view holder
}
override fun onBindViewHolder(p0: MyViewHolder, p1: Int) {
p0.bindItem(myList[p1], fragment)
}
override fun getItemCount(): Int {
return myList.size
}
class MyViewHolder(view:View):RecyclerView.ViewHolder(view){
fun bindItem(item:MyModel,frag:FragmentOne)=with(itemView){
setOnClickListener{
frag.onItemSelected(item)
}
}
}
}
Related
I have recyclerView and after click of card I would like to replace fragments in activity. The problem is I have no access to activity. Here is my code in adapter:
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
val itemsViewModel = mList[position]
holder.tagImage.setImageResource(itemsViewModel.tagImage)
holder.tagName.text = itemsViewModel.tagName
holder.tagDescription.text = itemsViewModel.tagDescription
holder.itemView.setOnClickListener {
Log.d(InTorry.TAG, itemsViewModel.tagName)
val fragment = ProductsFragment()
val transaction = activity?.supportFragmentManager?.beginTransaction()
transaction?.replace(R.id.homeFragmentsContainer, fragment)
//transaction?.disallowAddToBackStack()
transaction?.commit()
}
}
The above replace code works in fragment but in adapter there is "activity?" error.
Kind Regards
Jack
There are multiple ways to solve this problem.
Using Context
You can use the context from holder.itemView and cast it into an Activity.
This is probably the simplest way, however this can be problematic since a Context may represent an Activity, a Service, an Application, etc. in which case it may lead to a ClassCastException when used simply.
Using Callback
You can set up a callback from your Adapter to your Activity or Fragment and then replace your Fragment.
Use JetPack Navigation
This is my personal favorite as the latest versions allow you to access NavController from Activity, Fragment or any View in the hierarchy to navigate. This is just one of many benefits of using this library.
Here is a link to Jetpack Navigation.
The Simplest and Safer way to solve this is my using Callback from your holder to activity. Below is the step by step process :
Decalare an Interface
interface OnItemClick {
fun onClick()
}
Implement that interface in you Activity and put the desired code
class MainActivity : OnItemClick {
...
override onClick() {
// Do whatever you want
val fragment = ProductsFragment()
val transaction = fragmentManager.beginTransaction()
transaction.replace(R.id.homeFragmentsContainer, fragment)
transaction.commit()
}
}
Create a variable of that Interface type in you Adapter and in your onBindViewHolder method invoke that interface
class MyAdapter(val listener : OnItemClick) {
...
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
...
listener.onClick()
}
Finally pass that interface to your Adapter from you Activity
class MainActivity : OnItemClick {
val adapter = MyAdapter(this)
...
}
NOTE : Please don't pass activity to context here and there you will get unexpected result and most probably a crash.
After watching this video https://www.youtube.com/watch?v=WqrpcWXBz14
I managed to do it this way
In Adapter
class TagsAdapter(var mList: List<TagsViewModel>) :
RecyclerView.Adapter<TagsAdapter.ViewHolder>() {
var onItemClick: ((TagsViewModel) -> Unit)? = null//click listener STEP 1!!!
override fun onBindViewHolder(holder: TagsAdapter.ViewHolder, position: Int) {
holder.itemView.setOnClickListener {
onItemClick?.invoke(itemsViewModel)//click listener STEP 2!!!
}
}
}
And in Fragment
class TagsFragment : Fragment() {
private lateinit var tagsRecyclerView: RecyclerView
private var tagsArray = ArrayList<TagsViewModel>()
private lateinit var adapter: TagsAdapter
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
adapter = TagsAdapter(tagsArray)
tagsRecyclerView.adapter = adapter
adapter.onItemClick = {//click listener STEP 3!!!
val fragment = ProductsFragment()
val transaction = activity?.supportFragmentManager?.beginTransaction()
transaction?.replace(R.id.homeFragmentsContainer, fragment)
//transaction?.disallowAddToBackStack()
transaction?.commit()
}
}
}
It looks very clean and easy. I don't know is a correct way but it works
I have a nav_graph, where Fragment 1 and Fragment 2 are defined.
Fragment1 has view pager with 3 tabs and each tab has recyclerview.
How can i navigate to Fragment 2 on item click of recyclerview ?
Fragment->ViewPager->Recyclerview->ClickAction.
In recyclerview's fragment, simply call requireParentFragment().findNavController().navigate(/* destination */) to navigate to Fragment2.
Besides, you should pass a lambda into your recycler view adapter and then pass it into your view holder for using it (i recommend this way).
You can read more about this in the sample code below.
class YourRecyclerViewAdapter(..., private val onItemClick: () -> Unit) : RecyclerView.Adapter<YourViewHolder>() {
override fun onCreateViewHolder(parent: ViewGroup) = YourViewHolder(onItemClick)
override fun onBindViewHolder(holder: YourViewHolder, position: Int) {
holder.bind(...)
}
}
class YourViewHolder(..., private val onItemClick: () -> Unit) : RecyclerView.ViewHolder(...) {
fun bind(...) {
// Use onItemClick here...
}
}
class RecyclerViewFragment : Fragment() {
override fun onViewCreated(view: View, saveInstanceState: Bundle?) {
val adapter = YourRecyclerViewAdapter(...) {
requireParentFragment().findNavController().navigate(...)
}
yourRecyclerView.adapter = adapter
}
}
This question already has answers here:
Get current fragment with ViewPager2
(18 answers)
Closed 1 year ago.
i used this ViewPager2 dependency:
implementation 'androidx.viewpager2:viewpager2:1.0.0'
then i wrote code and make Adapter for FragmentStateAdapter like below:
private inner class ViewPagerAdapter(fragmentManager: FragmentManager,
lifecycle: Lifecycle, config: TribunConfig?)
: FragmentStateAdapter(fragmentManager, lifecycle) {
private val config: TribunConfig?
override fun createFragment(position: Int): Fragment {
return TribunNewsFragment.newInstance(if (menuTribun!!.get(position) == "News") "home"
else menuTribun!!.get(position)!!.replace(" ", "").toLowerCase(), config)!!
}
override fun getItemCount(): Int {
return menuTribun!!.size
}
init {
this.config = config
}
}
an in method onCreate i implement some code related viewpager2 and its adapter like below:
viewPager2!!.offscreenPageLimit = 1
viewPager2!!.adapter = ViewPagerAdapter(supportFragmentManager, lifecycle, config)
TabLayoutMediator(tabLayout!!, viewPager2!!, TabLayoutMediator.TabConfigurationStrategy { tab: TabLayout.Tab?, position: Int -> tab!!.text = menuTribun!!.get(position) }).attach()
But i have problem when i want to make swipeRefresh. My swipeRefresh needs current fragment to access the method inside fragment. But there is NO METHOD getItem in FragmentStateAdapter. Anyone can help me?
Create property in your adapter to save all your fragments
Save current position
Get fragment from fragment list (property) by position when you need it
Something like this:
val fragments: MutableList<Fragment> = mutableListOf()
private val config: TribunConfig?
override fun createFragment(position: Int): Fragment {
val fragment = TribunNewsFragment.newInstance(if (menuTribun!!.get(position) == "News") "home"
else menuTribun!!.get(position)!!.replace(" ", "").toLowerCase(), config)!!
fragments.add(fragment)
return fragment
}
Parent:
yourTabLayout.addOnTabSelectedListener(object : TabLayout.OnTabSelectedListener {
override fun onTabSelected(tab: TabLayout.Tab?) {
val fragment = adapter.fragments[tab!!.position]
// cast fragment to your fragment class and do what you want
}
override fun onTabUnselected(tab: TabLayout.Tab?) {
}
override fun onTabReselected(tab: TabLayout.Tab?) {
}
})
Also you can use when or if-else for your fragments position or create base class with methods you need and cast getting fragment to it.
Following #Oleg's answer, you can access the Fragments like this in your Adapter instead of creating a local list and adding each fragment on createFragment method:
private val fragmentList: List<TribunNewsFragment>
get() {
return fragment.childFragmentManager.fragments.filterIsInstance<TribunNewsFragment>()
}
It's important to use childFragmentManager instead of fragmentManager if you are creating the ViewPager inside another fragment.
And it survives configuration changes, regarding #lawonga's comment on the answer's topic.
I have a Pager Adapter to load server images as like below:
class ImagePagerAdapter(var imageList: List<ProductImageModel>, var fragmentManager: FragmentManager) :
FragmentPagerAdapter(fragmentManager) {
override fun getItem(position: Int): Fragment {
val fragment = ImagePagerFragment()
fragment.arguments = Bundle().apply {
putString(PAGER_IMAGE_URL, imageList[position].image)
}
return fragment
}
override fun getCount(): Int {
return imageList.size
}
}
I want to know any option to addtoBackStack(null) while creating a new Fragment inside the pagerAdapter.
The current code, automatically adding the fragments to the backstack. So it's making a huge issue while the user presses the back button. I have added another logic to filter the fragments in the back press.
I'm starting in Kotling and I don't know how to change between fragments, I have tried this code:
val manager = supportFragmentManager
val transaction = manager.beginTransaction()
transaction.add(R.layout.fragment_information.toInt(), ComplainFragment())
transaction.commit()
R.layout.fragment_information.toInt()
But i have an error with this parameter because it doesn't find the fragment Id.
I usually use replace to change between fragments. Also change R.layout.fragment_information to R.id.fragment_layout_id only, so no need toInt()
transaction.replace(R.id.fragment_layout_id, fragment)
Here is my suggestion.
var fragment: Fragment? = null
when (itemId) {
R.id.fragment_information -> {
fragment = ComplainFragment()
}
}
if (fragment != null) {
val transaction = supportFragmentManager.beginTransaction()
transaction.replace(R.id.fragment_layout_id, fragment)
transaction.commit()
}
The other answers will work but still we can improve a lot by using extension functions in Kotlin.
Add an extension function to the FragmentManager class like below,
inline fun FragmentManager.doTransaction(func: FragmentTransaction.() ->
FragmentTransaction) {
beginTransaction().func().commit()
}
then create an extension function to the AppCompatActivity class,
fun AppCompatActivity.addFragment(frameId: Int, fragment: Fragment){
supportFragmentManager.doTransaction { add(frameId, fragment) }
}
fun AppCompatActivity.replaceFragment(frameId: Int, fragment: Fragment) {
supportFragmentManager.doTransaction{replace(frameId, fragment)}
}
fun AppCompatActivity.removeFragment(fragment: Fragment) {
supportFragmentManager.doTransaction{remove(fragment)}
}
Now, to add and remove fragments from any activity, you just need to call like this,
addFragment(R.id.fragment_container, fragment)
replaceFragment(R.id.fragment_container, fragment)
please refer the below link for more info,
https://medium.com/thoughts-overflow/how-to-add-a-fragment-in-kotlin-way-73203c5a450b
This is an example for you to go to a fragment or activity by clicking a button inside another fragment:
class Fragment_One: Fragment() {
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
// Inflate the layout for this fragment
return inflater.inflate(R.layout.fragment_one, container, false)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
btn_goToActivity2.setOnClickListener {
val intent = Intent(context, SecondActivity::class.java)
startActivity(intent)
}
btn_goToFragment2.setOnClickListener {
var fr = getFragmentManager()?.beginTransaction()
fr?.replace(R.id.fragment, Fragment_Two())
fr?.commit()
}
}
}
When you add a fragment, you need to add it to an ID that exists in your Activity's layout, not an entire layout:
supportFragmentManager.beginTransaction().add(R.id.some_id_in_your_activity_layout, ComplainFragment()).commit()
In case anyone still needs a quick approach to this. I created a function than can be easily called whenever you need to change a fragment.
private fun replaceFragment(fragment: Fragment) {
val transaction = supportFragmentManager.beginTransaction()
transaction.replace(R.id.frame, fragment)
transaction.commit()
}
R.id.frame in this case is the id of my Framelayout in the activity that will hold my fragment. All you have to do now is call the function.
replaceFragment(HomeFragment())
private fun transitionFragment(fragment: Fragment) {
val transition = requireActivity().supportFragmentManager.beginTransaction()
transition.replace(R.id.fragment_container_create_void_parent, fragment)
.addToBackStack(null).commit()
}
fragment-ktx jetpack library contains convenient extension functions which simplify many things, including transactions:
// MyActivity.kt
class MyActivity : AppCompatActivity() {
...
fun showMyFragment() {
val fragment = MyFragment()
supportFragmentManager.commit {
replace(R.id.fragment_container, fragment)
}
}
}
R.id.fragment_container it's an id of a fragment container in the parent layout. There's FragmentContainerView which is the recommended container, for example:
<!-- my_activity_layout.xml -->
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout ...>
<androidx.fragment.app.FragmentContainerView
android:id="#+id/fragment_container"
... />
...
</androidx.constraintlayout.widget.ConstraintLayout>
But if your purpose is to implement in-app navigation, it's better and much easier to use Navigation component instead of manually switching fragments.
this is my solution for Change current fragment to orther in kotlin:
val supportFragment = SupportFragment()
requireActivity().supportFragmentManager.beginTransaction()
.add(this.id, supportFragment)
.addToBackStack("ok")
.commit()