I came up with something new, trying to use 2 Fragment Manager for same activity.
So for that I did some customization with help of FragmentHostCallback<{Activity}> and FragmentConroller class.
val fmCallBack_1 = DemoHostCallBack(this, Handler(Looper.getMainLooper()),0)
val fmController_1 = FragmentController.createController(fmCallBack_1)
fmController_1.attachHost(null)
fragmentManager_1 = fmController_1.supportFragmentManager
val fmCallBack_2 = DemoHostCallBack(this, Handler(Looper.getMainLooper()),0)
val fmController_2 = FragmentController.createController(fmCallBack_2)
fmController_2.attachHost(null)
fragmentManager_2 = fmController_2.supportFragmentManager
I am getting 2 different hash code for both of them.
Now as per my design
It contain
2 cards
On top of card it contain 5 Tabs
Green box will display FragmentResult. I am using instance of this fragment in second card too
in same activity.
The Problem
I used supportFragmentManager with a ContainerView to use multiple instance of FragmentResult in first card. It worked and it shows different result on change of tab.
But when I tried to show some data over second card tabs, It is not rendering result in second card, else supportFragmentManager showing it in First Card which I don't want.
Then I thought to create 2 Fragment Manager for a single Activity and use 2 Container View one for each to perform add or replace Fragment Transaction. But now no Fragment instance is loading to any card.
I think issue is with 2 custom Fragment manager which is not attached to host i.e. activity. How to achieve this, I spent 3 days on this.
private fun showCard_1_FragmentContainer(tag:String,fragment: Fragment,containerId:Int){
val existFragment = fragmentManager_1.findFragmentByTag(tag)
if(existFragment == null){
fragmentManager_1.beginTransaction()
.addToBackStack(null)
.replace(containerId, fragment,tag)
.commit()
}else{
fragmentManager_1.beginTransaction()
.addToBackStack(null)
.replace(containerId, existFragment,tag)
.commit()
}
}
private fun showCard_2_FragmentContainer(tag:String,fragment: Fragment,containerId:Int){
val existFragment = fragmentManager_2.findFragmentByTag(tag)
if(existFragment == null){
fragmentManager_2.beginTransaction()
.addToBackStack(null)
.replace(containerId, fragment,tag)
.commit()
}else{
fragmentManager_2.beginTransaction()
.addToBackStack(null)
.replace(containerId, existFragment,tag)
.commit()
}
}
private fun launchFragments(tag:String,fragment: Fragment,cardType:String){
when(cardType){
MConstants.CARD_1-> {showCard_1_FragmentContainer(tag,fragment,R.id.containerOne)}
MConstants.CARD_2-> {showCard_2_FragmentContainer(tag,fragment,R.id.containerTwo)}
}
}
Please help me on this with any other approach you have.
Related
So, I have this code inside a setOnClickListener:
helpFragment = HelpFragment.newInstance()
supportFragmentManager
.beginTransaction() // Começar a transição
.replace(R.id.container, helpFragment)
.addToBackStack(helpFragment.toString())
.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN)
.commit() // Aplicar as alterações
}
But the problem is, every time I click the button a new instance of the fragment is instantiated. With that, for example, if I click 10 times in the button, I will have 9 fragments added to backstack and 1 visible. How can I create just one instance of the fragment? I have tried:
if (helpFragment == null)
But that obviously doesn't work...
Adding a Fragment to the back stack will retain fragments in the stack so that you can navigate back when needed.
You can still use the back stack but you have to check if the fragment is already added so that you don't have duplicated instances of the fragment in the stack.
E.g.
val helpFragment = HelpFragment.newInstance()
val isInBackstack = supportFragmentManager.findFragmentByTag(helpFragment.toString())
if (!isInBackstack) {
supportFragmentManager
.beginTransaction() // Começar a transição
.replace(R.id.container, helpFragment)
.addToBackStack(helpFragment.toString())
.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN)
.commit() // Aplicar as alterações
}
If you use addToBackStack, it'll always save the fragments to the backstack. Remove that line to not add the fragment to backstack. addToBackStack is used when there are multiple changes to the transaction, then all the changes are added in stack, and pressing back button will then restore those transactions one by one.
helpFragment = HelpFragment.newInstance()
supportFragmentManager
.beginTransaction() // Começar a transição
.replace(R.id.container, helpFragment)
.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN)
.commit() // Aplicar as alterações
}
It is better to see whole codebase of your problem. You can solve it with Kotlin's lazy. Check out this topic
My Navigation graph has two destinations, a Fragment and a DialogFragment. The Fragment contains a Button that navigates to the DialogFragment when pressed.
Everything works as expected, except if I click the button very quickly. Doing so can trigger a
IllegalArgumentException: navigation destination
com.example.app:id/show_dialog is unknown to this NavController
To fix this, I ensure that the current destination is the Fragment containing the show_dialog action:
val navController = findNavController()
val currentDest = navController.currentDestination?.id
if (currentDest == R.id.test_fragment) {
navController.navigate(TestFragmentDirections.showDialog())
}
Making this change appears to fix the issue. However, I would like to know:
Why is it necessary to wrap the navigate call with a conditional statement in this situation?
Your question is just based on the Android inside Architecture and is also dependent on the hardware performance. Just wrap it in a try/catch block:
try{
findNavController().navigate(TestFragmentDirections.showDialog())
}catch(e: IllegalArgumentException){
e.printStackTrace
}
You might be getting IllegalArgumentException because if you see showDialog() method, you will find implementation as below:
public void show(FragmentManager manager, String tag) {
mDismissed = false;
mShownByMe = true;
FragmentTransaction ft = manager.beginTransaction();
ft.add(this, tag);
ft.commit();
}
I'll suggest to write your own showDialog menthod with below implementation:
fragmentManager.beginTransaction()
.add(dialog, "TAG")
.commitAllowingStateLoss();
I am using drawer menu in my app.I select one option from menu and open fragment and from that fragment call an Activity.Since here it is working fine but when I press back button(OnbackPress) then app is crashed.
bellow is the error.
"Unable start activity...ClassCastException...cannot be cast to Home_Tab"
This is MainActivity code.
if (savedInstanceState == null) {
homefragment = Home_tab()
fragmentTransaction = fragmentManager!!.beginTransaction()
fragmentTransaction!!.replace(R.id.frame, homefragment)
fragmentTransaction!!.addToBackStack(null)
fragmentTransaction!!.commit()
} else {
homefragment = supportFragmentManager.fragments[0] as Home_tab //Crash at this line
}
Code from where backPress Called.
override fun onBackPressed() {
super.onBackPressed()
finish()
}
//Add a check like this before casting.
//It is a smart cast and you can directly use the result.
Fragment fragmentZero = supportFragmentManager.fragments[0]
if (fragmentZero is Home_tab) {
//Casting is done, you can directly use fragment here
homefragment = fragmentZero
}
Well based on your code you add the fragment to the fragmentmanager (regular one) but try to get it back from the supportfragmentmanager. Those are two different classes and your fragment can only extend one
You're are mixin fragment manager et support fragment manager, i'll go with suppport one since this is the right way to do it. To get current display fragment added with a container ID use findFragmentById
if (savedInstanceState == null) {
homefragment = Home_tab()
supportFragmentManager?.let{
fragmentTransaction = it.beginTransaction()
fragmentTransaction.replace(R.id.frame, homefragment)
fragmentTransaction.addToBackStack(null)
fragmentTransaction.commit()
}
} else {
homefragment = supportFragmentManager.findFragmentById(R.id.frame) as Home_tab
}
I am trying to tell when a user selects a different fragment in my navigation drawer. I was trying to use
override fun setUserVisibleHint(isVisibleToUser: Boolean) {
super.setUserVisibleHint(isVisibleToUser)
}
How i switch fragments in my MainActivity:
override fun onNavigationItemSelected(item: MenuItem): Boolean {
// Handle navigation view item clicks here.
when (item.itemId) {
R.id.nav_camera -> {
// Handle the camera action
val fragment: HomeFragment = HomeFragment()
supportFragmentManager.beginTransaction().replace(R.id.content_main, fragment).commit()
}
R.id.nav_manage -> {
val fragment: SettingFragment = SettingFragment()
fragmentManager.beginTransaction().replace(R.id.content_main, fragment).commit()
}
R.id.nav_share -> {
onInviteClicked()
}
R.id.nav_send -> {
val emailIntent: Intent = Intent(android.content.Intent.ACTION_SEND)
emailIntent.type = Constants.FEEDBACK_EMAIL_TYPE
emailIntent.putExtra(android.content.Intent.EXTRA_EMAIL,
arrayOf(Constants.FEEDBACK_EMAIL_ADDRESS))
emailIntent.putExtra(android.content.Intent.EXTRA_SUBJECT,
Constants.FEEDBACK_EMAIL_SUBJECT)
startActivity(Intent.createChooser(
emailIntent, Constants.FEEDBACK_TITLE))
}
}
val drawer: DrawerLayout = findViewById(R.id.drawer_layout)
drawer.closeDrawer(GravityCompat.START)
return true
}
However this does not seem to get called at all. For example, in my NavigationDrawer activity, it shows Fragment A. The user opens the navigation drawer and selects Fragment B. setUserVisibleHint() does not get called in fragment A so my code can know it is no longer shown. I need my code that is isolated in fragment A to know when it is not shown so it can call .stop() on some variables. This is the same use case as onPause() in an activity.
You can simply call
if (myFragment.isVisible()) {...}
or another way is
public boolean isFragmentUIActive() {
return isAdded() && !isDetached() && !isRemoving();
}
Here are a few things I can think of...
Use a consistent fragment, either Support or Native, not both. And, some say the Support fragment is preferable (better maintained).
Make sure the fragment container is not hard coded in XML. If you intend to replace a fragment, then the initial fragment should be loaded dynamically by your code (you typically will load into a FrameLayout using the id as your R.id.{frameLayoutId}).
Do Use the Frament lifecycle events. onPause fires when you replace a fragment, so does onDetach. That will tell you when your old fragment is no longer visible (or will be invisible shortly). If it does not fire, then you have another issue in your code, possibly mixing of Fragment types, or a hardcoded fragment in XML?
Use setUserVisibleHint only in a fragment pager, or be prepared to set it manually. this answer has a little more to say about the use of setUserVisibleHint. When using a pager, multiple fragments can be attached at once, so an additional means (some call it lifecycle event) was needed to tell if a fragment was "really, truly" visible, hence setUserVisibleHint was introduced.
Bonus: If appropriate for your app, use the back stack for backing up by calling addToBackStack after replace. I add this mainly as an addition lifecycle item one would typically want in their app. The code looks like this...
// to initialize your fragment container
supportFragmentManager
.beginTransaction()
.add(R.id.content_fragment, fragment)
.addToBackStack("blank")
.commit()
// to update your fragment container
supportFragmentManager
.beginTransaction()
.replace(R.id.content_fragment, fragment)
.addToBackStack("settings")
.commit()
//in your XML, it can be as simple as adding the FrameLayout below,
// if you start with the Android Studio template for Navigation drawer,
// you can replace the call that includes the "content_main" layout
<!--<include layout="#layout/content_main" /> -->
<FrameLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="#+id/content_fragment" />
I hope this helps.
I have a class derived from FragmentPagerAdapter and user swiping works fine (4 tabs), but I would like to show a page/tab/fragment from code. For example, when user taps a textview, I would like to switch to a specific fragment.
I've tried the following without success:
public void onClockClick(View v)
{
FragmentManager fm = getFragmentManager();
if (fm != null) {
fm.beginTransaction()
.setCustomAnimations(android.R.animator.fade_in, android.R.animator.fade_out)
.show(mainFragment)
.commit();
}
}
Am I barking up the wrong tree?
Well, if you know the position of the fragment you want to show then you can simply do: pager.setCurrentItem(pos)
This will automatically animate to the fragment at the desired position. Simple.
If you want to add another fragment to the existing FragmentPagerAdapter, then you'll need to first add the fragment's instance to that adapter and then call notifiyDataSetChanged on it.