Fragment Backstack Toolbar title - android

Is there an efficient way to automatically set the toolbar's title when adding/replacing fragments as well as popping fragments from the backstack?
I have implemented this abstract method in my BaseFragment class:
abstract fun header() : String
override fun onResume() {
super.onResume()
(activity as SSBaseActivity).header.text = header()
}
and I modify the header in each Fragment that inherits from my BaseFragment class and displays the value in onResume but I noticed that when I press back, the last title set isn't being replaced from the fragment currently in the stack.

You could do this by using an OnBackStackChangedListener in your Activity:
supportFragmentManager.addOnBackStackChangedListener {
val fragment = supportFragmentManager.findFragmentById(R.id.yourFragmentFrame)
if (fragment is BaseFragment) {
header.text = fragment.header()
}
}

Related

Navigation in a recycler view between fragments

I am making a simple app with a recycler containing a list of items, and when an item is clicked, it needs to navigate to a new separate page for that item.(Note that the recycler view with list of items and the item page are both fragments and the navigation needs to happen without starting a new activity)
I have set up the NavHostFragment in MainActivity and the onClickListener to get the item clicked in the onBindViewHolder method of Adapter classs of the fragment with recycler view. I cannot figure out how to call the navController in MainACtivity within the onClickListener method in Adapter class to navigate to the item fragment, or is there any other way to do this? I am newbie android studio dev! Please help, thanks!
You can simply implement an interface to make communication between fragment and adapter.
Suppose there is FragmentA, FragmentB, and an Adapter.
FragmentA and adapter are related and you want to move from FragmentA to FragmentB. So you connect FragmentA with the adapter using an interface listener. Handle the click event in the adapter and pass data from adapter to FragmentA and from override method in FragmentA call FragmentB using nav controller.
Here is simple example:
Listener:
public interface eventListener { void onEvent(int key, Object object); }
FragmentA:
class FragmentA : Fragment(), eventListener {
override fun onEvent(key: Int, any: Any?) { // call FragmentB }
}
Adapter:
class Adapter(listener: WoovlyEventListener) :
RecyclerView.Adapter<RecyclerView.ViewHolder>() {
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
holder.btn.setOnClickListener{ listener.onEvent(0, null) }
}
}
FragmentB:
class FragmentB : Fragment(){}

Fragment instances are kept in viewPager2 when host fragment removed

Have one activity app, where I have HomeFragment from which I open HostFragment, and HostFragment has ViewPager2 with 3 fragment items TabFragment1, TabFragment2 and TabFragment3.
When I open the HostFragment, and then go back, my tabFragments' instances are not removed. Below is the logs from lifecycles
navController.navigate(directionToHostFragment)
HostFragment OnStart HostFragment{c37542c}
TabFragment1 OnStart TabFragment1 {f41b08}
navController.popBackStack()
HostFragment OnPause HostFragment{c37542c}
HostFragment OnStop{c37542c}
HostFragment OnDestroy {c37542c}
navController.navigate(directionToHostFragment)
Blockquote
HostFragment OnStart{2670c03}
TabFragment1 OnStart{f45b87d}
navController.popBackStack()
HostFragment OnPause{2670c03}
HostFragment OnStop{2670c03}
HostFragment onDestroy{2670c03}
From ids you can see that there is a new TabFragment1 instance and old one haven't destroyed(I have logs in onPause(), onStop() etc.).
Some code snippets:
Adapter for viewPager2 I used -
class LessonTabsAdapter(fragmentActivity: FragmentActivity, val pages: List<BaseFragment>) :
FragmentStateAdapter(fragmentActivity) {
override fun getItemCount() = pages.size
override fun createFragment(position: Int) = pages[position]
}
Some part from TabFragment1
class TabFragment1 : BaseFragment(R.layout.fragment_tab_1) {
private var mediaPlayer: MediaPlayerClass? = null
private lateinit var player: SimpleExoPlayer
private val viewModel by sharedViewModel<SomeViewModel>()
private val binding by viewDataBinding(FragmentTab1Binding::bind)
//Overriden onViewCreated(..) and some private methods
}
So any hints how to deal with this problem?
When you open the HostFragment the ViewPager2 creates a fragment in the lifecycle provided. In this case you are passing activity as the parameter so, it is creating the fragments in Activity's lifecycle. You can check the same by printing the activity?.supportFragmentManager?.fragments list. If you want to couple the ViewPager2's childs with HostFRagment, you need to pass the fragments instance to the FragmentStateAdapter. Check the function 2 that I have added. The best way to implement the ViewPager2 is by using function 3.
1..
public FragmentStateAdapter(#NonNull FragmentActivity fragmentActivity) {
this(fragmentActivity.getSupportFragmentManager(),fragmentActivity.getLifecycle());
}
2.//
public FragmentStateAdapter(#NonNull Fragment fragment) {
this(fragment.getChildFragmentManager(), fragment.getLifecycle());
}
3.//
public FragmentStateAdapter(#NonNull FragmentManager fragmentManager, #NonNull Lifecycle lifecycle) {
mFragmentManager = fragmentManager;
mLifecycle = lifecycle;
super.setHasStableIds(true);
}

How to get parent Fragment from ViewPager Fragment that is an AndroidEntryPoint?

I have a Fragment within a ViewPager that is created like this:
companion object {
fun newInstance(someData: SomeData): ViewPagerFragment {
val f = ViewPagerFragment()
val args = Bundle()
args.putParcelable("someData", someData)
f.arguments = args
return f
}
}
It is also a Hilt entry point, and it creates its own ViewModel, but also needs the parent Fragments ViewModel, so the top of the class looks like this:
#AndroidEntryPoint
class ViewPagerFragment : Fragment() {
private val parentViewModel: ParentViewModel by viewModels(
ownerProducer = { requireParentFragment() }
)
private val viewPagerViewModel: ViewPagerViewModel by viewModels()
However, the parent Fragment is not found:
java.lang.IllegalStateException: Fragment ViewPagerFragment{b6d6157}
(e5f3fc7d-2ae4-4275-9763-22826a9be939) id=0x7f09017e} is not a child Fragment, it is
directly attached to
dagger.hilt.android.internal.managers.ViewComponentManager$FragmentContextWrapper#1852444
I think this is happening because after adding the viewPagerViewModel the generated ViewPagerFragment class is not a childFragment. Is there a workaround to get the parent Fragment and ultimately get the parent ViewModel? The latter is the main goal.
For now I use ViewPager, not ViewPager2, because it's a legacy infinite wrapper ViewPager.
EDIT:
The ViewPagerAdapter is created like this:
val pagerAdapter = VpPagerAdapter(
requireActivity().supportFragmentManager,
someData,
)
When you create your ViewPager adapter, you need to pass in the childFragmentManager if you want the fragments in the ViewPager to be considered child fragments.

How to avoid duplicate code in the activity and fragment?

When an activity is launched, the condition is checked in it, if the condition is true, the openNewActivity method is called and the current activity ends. But if the condition is false, then a transition is made to the fragment, inside which this condition can become true and then you need to call the openNewActivity method, which is defined in the activity. I do not want to duplicate this method in the fragment, how to properly implement a call to this method from the fragment? What are the best practices in such cases?
Activity
class FirstActivity : AppCompatActivity(), MyInterface {
override fun onSomethingDone() { //This function gets called when the condition is true
openNewActivity()
}
override fun onAttachFragment(fragment: Fragment) { //A fragment MUST never know it's an activity, so we exposed fragment interface member to easily initialize it whenever the fragment is attached to the activity.
when (fragment) {
is MyFragment -> fragment.myInterface = this
}
}
override fun onCreate() {
super.onCreate()
}
private fun openNewActivity() {
//opens a new activity
}
}
Interface
interface MyInterface {
fun onSomethingDone()
}
Fragment
class MyFragment : Fragment() {
var myInterface: MyInterface? = null
override fun onCreate() {
if (somethingIsTrue)
myInterface.onSomethingDone() //condition is true, call the interface method to inform the activity that the condition is true and the new activity should be opened.
}
}
Create an interface. Initialize the fragment's interface in the activity's onAttachFragment for the reason mentioned in the code. This way, the function for starting a new activity is defined only in the activity and does not need to be duplicated in the fragment.

Which Fragment Manager is needed for TimePickerDialogue Fragment kotlin

I am trying to implement a simple TimePickerDialogue fragment, which displays when a button in pressed in a layout which is also a fragment. The Android developer guide shows this in Java:
public void showTimePickerDialog(View v) {
DialogFragment newFragment = new TimePickerFragment();
newFragment.show(getSupportFragmentManager(), "timePicker");
}
and says:
The show() method requires an instance of FragmentManager and a unique name for the fragment.
When I converted this to Kotlin there is no getSupportFragmentManager method offered. What should I use instead?
class AlertsFragment : Fragment() {
fun showTimePickerDialog(v: View) {
val newFragment = TimePickerFragment()
newFragment.show(FragmentManager(), "timePicker") // WHAT FRAGMENTMGR???
}
}
I am importing android.support.v4.app.Fragment
My MainActivity will display the TimePickerDialogue fragment as well as the Fragment that has a button to open the TimePickerDialogue. MainActivity has a tablayout using fragments.
Does anything need to be changed in MainActivity to make the TimePickerDialogue show() function work in the AlertsFragment?
class MainActivity : FragmentActivity(){
private lateinit var pagerAdapter: TabPagerAdapter
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
pagerAdapter = TabPagerAdapter(supportFragmentManager)
pagerAdapter.addFragments(WorkoutGridFragment(), "Workouts")
pagerAdapter.addFragments(AlertsFragment(), "Reminders")
pagerAdapter.addFragments(AboutFragment(), "About")
// customViewPager is the viewpager created in the activity_main xml layout
customViewPager.adapter = pagerAdapter
// set up the viewpager with the tablayout
customTabLayout.setupWithViewPager(customViewPager)
}
}
Fragments don't have a SupportFragmentManager field or getter. This applies whether you write it in Kotlin or Java.
Activities, however, do. So call the activity and get the supportFragmentManager:
fun showTimePickerDialog(v: View) {
val newFragment = TimePickerFragment()
newFragment.show(activity.supportFragmentManager, "timePicker")
}
Also, if this is what you were reading in the docs, you'll see this:
Also make sure that your activity that displays the time picker extends FragmentActivity instead of the standard Activity class. (emphasis mine)
Which implies the showTimePickerDialog method is defined in an Activity. And don't get this wrong, I'm not saying you have to define it in an activity, but since that's what they do in the docs, they can call the SupportFragmentManager directly. But if you call it from a fragment or anywhere outside an activity, you need an activity instance.

Categories

Resources