I have a fragment which contains a ViewPager2, It has 3 child fragments, I want to receive an reference viewpager in child fragment to be able to change viewPager programmatically. Since parent fragment which contains viewPager is not activity I can not access the viewPager with findViewById. On another question a usersuggested val pager = container as ViewPager2 but it's returning null. Can someone help me with this. As seen in code snippets, I am trying to get an reference to viewPager in BillingFragment from OneFragment.
Please let me know if you need to see ViewPagerAdapter code snippet.
Child fragment
class OneFragment : Fragment() {
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
// Inflate the layout for this fragment
val view = inflater.inflate(R.layout.fragment_billing, container, false)
val pager = container as ViewPager2
// ^^^^^ page is null
return view;
}
ViewPager main fragment
class BillingFragment : Fragment() {
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
// Inflate the layout for this fragment
val view = inflater.inflate(R.layout.fragment_billing_main, container, false)
setupStepView(view)
setupViewPager(view)
setupButtons(view)
return view
}
private fun setupStepView(view: View) {
view.stepView.state
// You should specify only stepsNumber or steps array of strings.
// In case you specify both steps array is chosen.
.steps(
listOf(
"First Step",
"Second Step",
"Third Step"
)
) // You should specify only steps number or steps array of strings.
// In case you specify both steps array is chosen.
.stepsNumber(3)
.animationDuration(resources.getInteger(android.R.integer.config_shortAnimTime))
.commit()
view.stepView.setOnStepClickListener { position ->
// viewPager.setCurrentItem(position, false)
}
}
private fun setupViewPager(view: View) {
// viewPager = view.viewPager;
view.viewPager.adapter = ViewPagerAdapter(childFragmentManager, lifecycle)
view.viewPager.registerOnPageChangeCallback(
object : ViewPager2.OnPageChangeCallback() {
override fun onPageSelected(position: Int) {
super.onPageSelected(position)
view.stepView.go(position, true)
setButtons(position, view)
}
}
)
}
private fun setupButtons(view: View) {
view.backButton.setOnClickListener {
view.viewPager.setCurrentItem(view.viewPager.currentItem - 1, false)
}
view.nextButton.setOnClickListener {
view.viewPager.setCurrentItem(view.viewPager.currentItem + 1, false)
}
}
private fun setButtons(position: Int, view: View) {
when (position) {
0 -> {
view.backButton.visibility = View.INVISIBLE
view.nextButton.visibility = View.VISIBLE
}
1 -> {
view.backButton.visibility = View.VISIBLE
view.nextButton.visibility = View.VISIBLE
}
2 -> {
view.backButton.visibility = View.VISIBLE
view.nextButton.visibility = View.INVISIBLE
}
}
}
}
Accessing the child fragment's view in parent fragment is not a good idea. If you want to trigger any action from the fragment. You can follow these steps:
class MyViewPagerAdapter(...) : FragmentStateAdapter(...){
lsit : List<Fragment>
fun getItem(position :Int) = list[position]
}
class OneFragment : Fragment(){
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
// Inflate the layout for this fragment
val view = inflater.inflate(R.layout.fragment_billing, container, false)
val position = viewPager2.currentItem
(adapter.getItem(position) as? BillingFragment)?.
return view;
}
}
class BillingFragment : Fragment(){
override fun onViewCreated(...){
...
}
// pass the data in the parameter that you want to use or update
fun updateViewPager(position : Int){
// perform your actions
view.viewPager.currentItem = position
}
}
Alternatives
If you know about EventBus, then you can use EventBus.
The android's default way is to use BroadcastReceiver.
Related
Problem:
i have a layout like this:
in the parent fragment i have a searchview responsible for filtering the recyclerviews inside the child fragments.
the child fragments are inside a tablayout viewpager combination.
so the OnQueryTextListener is inside the parent fragment and i have to get an instance of the recyclerviews adapters from child fragments to do the filtering.
what is the proper way to do this?
What I've tried:
i searched and found getChildFramgentManager which returns an instance of the child fragment. but apparently, the fragments should be created dynamically? im not sure how that works with viewpager. so if there is a way to do this with getChildFramgentManager and viewpager please explain.
My code:
everything I've written is the standard procedure and im not that far into the project to add something that changes the outcome so im not gonna take your time by adding the code, but if the code is necessary please say so and i will add it.
In your viewpager's adapter you can store references to child fragments into a list and iterate through child fragments to call a method.
Something like this :
Parent fragment
class ParentFragment : Fragment() {
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
val view = inflater.inflate(R.layout.parent_fragment, container, false)
val search : SearchView = findViewById(R.id.search)
val pager : ViewPager = findViewById(R.id.pager)
val adapter = ViewPagerAdapter(supportFragmentManager)
pager.adapter = adapter
search.setOnQueryTextListener(object : SearchView.OnQueryTextListener {
override fun onQueryTextSubmit(query: String?): Boolean {
adapter.filter(query ?: "")
return true
}
override fun onQueryTextChange(newText: String?): Boolean {
return true
}
})
return view
}
}
Pager adapter
class ViewPagerAdapter(fm: FragmentManager) : FragmentStatePagerAdapter(fm) {
private var mList : MutableList<ChildFragment> = ArrayList()
init {
mList.add(ChildFragment())
mList.add(ChildFragment())
}
fun filter(text: String) {
mList.forEach {
it.filter(text)
}
}
override fun getItem(position: Int): Fragment {
return mList.get(position)
}
override fun getCount(): Int {
return mList.size
}
}
Child fragment
class ChildFragment : Fragment() {
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
val view = inflater.inflate(R.layout.child_fragment, container, false)
// get reference to recyclerview, set adapter...
return view
}
fun filter(text: String) {
// do job here
}
}
I have a fragment called MainFragment that contains a ViewPager that contains another fragment called LibraryFragment.
LibraryFragment contains a RecyclerView with a list of items that contain an ImageView. The ImageView's contents are loaded with Coil.
When an item is clicked, LibraryFragment navigates to another fragment called ArtistDetailFragment and uses the ImageView from the item.
The problem is that while the enter transition works fine, the ImageView does not return to the list item when navigating back and only fades away. Ive attached an example below:
Ive tried using postponeEnterTransition() and startPostponedEnterTransition() along with adding a SharedElementCallback but neither have worked that well. I've also ruled out Coil being the issue.
Heres LibraryFragment:
class LibraryFragment : Fragment() {
private val musicModel: MusicViewModel by activityViewModels()
private val libraryModel: LibraryViewModel by activityViewModels()
private lateinit var binding: FragmentLibraryBinding
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
binding = FragmentLibraryBinding.inflate(inflater)
binding.libraryRecycler.adapter = ArtistAdapter(
musicModel.artists.value!!,
BindingClickListener { artist, itemBinding ->
navToArtist(artist, itemBinding)
}
)
return binding.root
}
private fun navToArtist(artist: Artist, itemBinding: ItemArtistBinding) {
// When navigation, pass the artistImage of the item as a shared element to create
// the image popup.
findNavController().navigate(
MainFragmentDirections.actionShowArtist(artist.id),
FragmentNavigatorExtras(
itemBinding.artistImage to itemBinding.artistImage.transitionName
)
)
}
}
Heres ArtistDetailFragment:
class ArtistDetailFragment : Fragment() {
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
val binding = FragmentArtistDetailBinding.inflate(inflater)
sharedElementEnterTransition = TransitionInflater.from(requireContext())
.inflateTransition(android.R.transition.move)
val musicModel: MusicViewModel by activityViewModels()
val artistId = ArtistDetailFragmentArgs.fromBundle(requireArguments()).artistId
// Get the transition name used by the recyclerview ite
binding.artistImage.transitionName = artistId.toString()
binding.artist = musicModel.artists.value?.find { it.id == artistId }
return binding.root
}
}
And heres the RecyclerView Adapter/ViewHolder:
class ArtistAdapter(
private val data: List<Artist>,
private val listener: BindingClickListener<Artist, ItemArtistBinding>
) : RecyclerView.Adapter<ArtistAdapter.ViewHolder>() {
override fun getItemCount(): Int = data.size
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
return ViewHolder(
ItemArtistBinding.inflate(LayoutInflater.from(parent.context))
)
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
holder.bind(data[position])
}
// Generic ViewHolder for an artist
inner class ViewHolder(
private val binding: ItemArtistBinding
) : RecyclerView.ViewHolder(binding.root) {
// Bind the view w/new data
fun bind(artist: Artist) {
binding.artist = artist
binding.root.setOnClickListener { listener.onClick(artist, binding) }
// Update the transition name with the new artist's ID.
binding.artistImage.transitionName = artist.id.toString()
binding.executePendingBindings()
}
}
}
EDIT: I used postponeEnterTransition and startPostponedEnterTransition like this:
// LibraryFragment
override fun onResume() {
super.onResume()
postponeEnterTransition()
// Refresh the parent adapter to make the image reappear
binding.libraryRecycler.adapter = artistAdapter
// Do the Pre-Draw listener
binding.libraryRecycler.viewTreeObserver.addOnPreDrawListener {
startPostponedEnterTransition()
true
}
}
This only makes the RecyclerView itself refresh however, the shared element still fades away instead of returning to the RecyclerView item.
Chris Banes says;
You may wonder why we set the OnPreDrawListener on the parent rather than the view itself. Well that is because your view may not actually be drawn, therefore the listener would never fire and the transaction would sit there postponed forever. To work around that we set the listener on the parent instead, which will (probably) be drawn.
https://chris.banes.dev/fragmented-transitions/
try this;
change
// LibraryFragment
override fun onResume() {
super.onResume()
postponeEnterTransition()
// Refresh the parent adapter to make the image reappear
binding.libraryRecycler.adapter = artistAdapter
// Do the Pre-Draw listener
binding.libraryRecycler.viewTreeObserver.addOnPreDrawListener {
startPostponedEnterTransition()
true
}
}
enter code here
to
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
postponeEnterTransition()
binding = FragmentLibraryBinding.inflate(inflater)
binding.libraryRecycler.adapter = ArtistAdapter(
musicModel.artists.value!!,
BindingClickListener { artist, itemBinding ->
navToArtist(artist, itemBinding)
}
)
(view?.parent as? ViewGroup)?.doOnPreDraw {
// Parent has been drawn. Start transitioning!
startPostponedEnterTransition()
}
return binding.root
}
So in this case, I have a fragment that act as the base fragment for a viewpager consisting two tabs. What I want to do is to pass an information from the base fragment to one of it's tab fragment.
This is the snippet code of the base fragment
BaseFragment.kt
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
val args = arguments?.let { SwitchingHistoryDetailFragmentArgs.fromBundle(it) }
val selectedOrderId = args?.orderId //This variable is the one that i want to pass.
// Set action bar title to "Main Dashboard"
(activity as AppCompatActivity).supportActionBar?.title = "Switching History Details"
val binding: FragmentSwitchingHistoryDetailBinding = DataBindingUtil.inflate(inflater,
R.layout.fragment_switching_history_detail, container, false)
Log.i("History", "Selected History: $selectedOrderId")
binding.viewpagerMain.adapter =
MyPagerAdapter((activity as AppCompatActivity).supportFragmentManager)
binding.tabsMain.setupWithViewPager(viewpager_main)
return binding.root
}
As you can see above, the variable that i want to pass is that selectedOrderId. This is the tab fragment that I want pass that information into:
DetailFragment.kt
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
// Binding object for this fragment and the layout
binding = DataBindingUtil.inflate(inflater,
R.layout.fragment_switching_history_detail_tab, container, false)
....
....
....
//Return.... i don't know.
return binding.root
}
And this is the PagerAdapter I'm using:
MyPagerAdapter.kt
class MyPagerAdapter(fm: FragmentManager): FragmentStatePagerAdapter(fm){
// sebuah list yang menampung objek Fragment
private val pages = listOf(
DetailFragment(), <- This is where i want to pass the information into
DetailFragment2()
)
// menentukan fragment yang akan dibuka pada posisi tertentu
override fun getItem(position: Int): Fragment {
return pages[position]
}
override fun getCount(): Int {
return pages.size
}
// judul untuk tabs
override fun getPageTitle(position: Int): CharSequence? {
return when(position){
0 -> "Details"
else -> "Previous Changes"
}
}
}
I'm quite new with viewPager. I tried to use Intent but does not work. Is there a way to achieve this? If there's any detail i miss to point out, feel free to ask.
Simple way is pass the selectedOrderId into the adapter like below:
binding.viewpagerMain.adapter =
MyPagerAdapter((activity as AppCompatActivity).supportFragmentManager,selectedOrderId)
then pass that orderId from PagerAdapter to fragment constructor like below:
class MyPagerAdapter(fm: FragmentManager,val orderId : String): FragmentStatePagerAdapter(fm){
// sebuah list yang menampung objek Fragment
private val pages = listOf(
DetailFragment.getInstance(orderId), <- This is where i want to pass the information into
DetailFragment2()
)
// menentukan fragment yang akan dibuka pada posisi tertentu
override fun getItem(position: Int): Fragment {
return pages[position]
}
override fun getCount(): Int {
return pages.size
}
// judul untuk tabs
override fun getPageTitle(position: Int): CharSequence? {
return when(position){
0 -> "Details"
else -> "Previous Changes"
}
}
}
Your Detail fragment looks like :
class DetailFragment : Fragment() {
private var orderId: String? = null
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
// Binding object for this fragment and the layout
binding = DataBindingUtil.inflate(inflater,
R.layout.fragment_switching_history_detail_tab, container, false)
if (arguments != null) {
orderId = arguments?.getString("orderId")
}
// do whatever you want to do with this variable
return binding.root
}
companion object {
/**
* Method used to get the instance of the fragment.
*
* #return fragment instance
*/
#JvmStatic
fun getInstance(orderId: String): DetailFragment {
val fragment = DetailFragment()
val bundle = Bundle()
bundle.putString("orderId", orderId)
fragment.arguments = bundle
return fragment
}
}
}
LET ME KNOW IF YOU HAVE ANY DOUBT IN THIS. HAPPY CODING :)
I have a fragment in MainActivity that handles other fragments inside a view page.
This is that one:
class FragOrdersHolder : Fragment() {
lateinit var viewPager: ViewPager
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?
): View? {
val mainView = inflater.inflate(R.layout.orders_holder_frg, container, false)
return mainView
}
override fun onViewCreated(mainView: View, savedInstanceState: Bundle?) {
super.onViewCreated(mainView, savedInstanceState)
viewPager = mainView.findViewById(R.id.ordersViewPagerID)
viewPager.adapter = OrdersPagerAdapter(activity!!.supportFragmentManager)
val pagerTab = mainView.findViewById<PagerTabStrip>(R.id.pagerHeaderID)
pagerTab.tabIndicatorColor = Color.WHITE
pagerTab.setTextColor(Color.WHITE)
}
fun GoTo(pos: Int) {
Handler().post(Runnable {
viewPager.setCurrentItem(pos, true)
})
}
}
I have 3 fragments in my viewPager and one of them, have a recyclerview inside it.
the recyclerview has a custom adapter class.
I want to go to next tap in viewPager when clicking on an item in the recylerview.
I'm calling the GoTo method in adapter class like this:
val fm = (activity as MainActivity).supportFragmentManager
val fragm = fm.findFragmentByTag("HOLDER") as FragOrdersHolder
fragm.GoTo(2)
I'd tried everything viewPager.setCurrentItem not working!
please help me.
I want to create a dialog which contain's ViewPager inside it which have 3 pages and all pages have different layout structure. I want a solution by that i can set the layout content programmatically . I think this can be done by making fragments for each page but i don't know how to do this.
I go through these answers but i am not getting idea how to use them in my case.
Viewpager in Dialog?
ViewPager in Custom Dialog
ViewPager in simple Dialog
You can try and build your custom dialog through DialogFragment. Consider the XML layout would contain a ViewPager and the code to go about would be:
class PagerDialog : DialogFragment() {
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
return inflater.inflate(R.layout.element_fragment_pager_dialog, container, false)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
setupPager()
}
private fun setupPager() {
val pagerFragment1 = PagerFragment1.newInstance()
val pagerFragment2 = PagerFragment2.newInstance()
val pagerFragment3 = PagerFragment3.newInstance()
viewPager?.adapter = MyFragmentPagerAdapter(childFragmentManager).apply {
adapterReference = object : PageAdapterInterface {
override var fragmentList: List<Fragment> =
mutableListOf(pagerFragment1, pagerFragment2, pagerFragment3)
}
}
}
companion object {
const val tag = "PagerDialog"
}
}
I have used reference to the list because it might cause leaks when not handled correctly. So the PagerAdapterInterface would look like:
interface PageAdapterInterface {
var fragmentList: List<Fragment>
fun getItemCount() = fragmentList.size
#Throws(StackOverflowError::class)
fun getItemAt(index: Int) : Fragment {
if (index >= fragmentList.size) throw StackOverflowError()
return fragmentList[index]
}
}
Your view pager adapter can make use of this reference in manner that is accessing referentially like:
class MyFragmentPagerAdapter(childFragmentManager: FragmentManager) : FragmentStatePagerAdapter(childFragmentManager){
lateinit var adapterReference: PageAdapterInterface
override fun getItem(p0: Int): Fragment = adapterReference.getItemAt(p0)
override fun getCount(): Int = adapterReference.getItemCount()
}
Finally in your Activity or Fragment on create() or onViewCreated() functions respectively, you can initialize the dialog as shown:
class MyActivity: AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
// use childFragmentManager if the code is
// used within the Fragment
val prev = supportFragmentManager.findFragmentByTag(PagerDialog.tag)
if (prev != null) {
supportFragmentManager.beginTransaction()
.remove(prev)
.addToBackStack(null)
.commit()
}
PagerDialog().show(supportFragmentManager, PagerDialog.tag)
}
}
Note: DialogFragment is deprecated on > API 28 check out https://developer.android.com/reference/android/app/DialogFragment