In my application I have fragment and I want to use this fragment into another fragment!
I write below codes, but when open fragment shows me force close error.
My code:
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
parentFragmentManager.beginTransaction().add(
R.id.calendarContainer, calendar, PersianCaldroidFragment::class.java.name
).commit()
}
Error message:
java.lang.IllegalStateException: FragmentManager is already executing transactions
at androidx.fragment.app.FragmentManager.ensureExecReady(FragmentManager.java:1686)
at androidx.fragment.app.FragmentManager.execSingleAction(FragmentManager.java:1716)
at androidx.fragment.app.BackStackRecord.commitNow(BackStackRecord.java:317)
at androidx.viewpager2.adapter.FragmentStateAdapter$FragmentMaxLifecycleEnforcer.updateFragmentMaxLifecycle(FragmentStateAdapter.java:726)
at androidx.viewpager2.adapter.FragmentStateAdapter$FragmentMaxLifecycleEnforcer$3.onStateChanged(FragmentStateAdapter.java:657)
at androidx.lifecycle.LifecycleRegistry$ObserverWithState.dispatchEvent(LifecycleRegistry.java:360)
at androidx.lifecycle.LifecycleRegistry.forwardPass(LifecycleRegistry.java:271)
at androidx.lifecycle.LifecycleRegistry.sync(LifecycleRegistry.java:313)
at androidx.lifecycle.LifecycleRegistry.moveToState(LifecycleRegistry.java:151)
at androidx.lifecycle.LifecycleRegistry.handleLifecycleEvent(LifecycleRegistry.java:134)
at androidx.fragment.app.Fragment.performStart(Fragment.java:3167)
at androidx.fragment.app.FragmentStateManager.start(FragmentStateManager.java:588)
at androidx.fragment.app.FragmentStateManager.moveToExpectedState(FragmentStateManager.java:279)
at androidx.fragment.app.FragmentStore.moveToExpectedState(FragmentStore.java:113)
at androidx.fragment.app.FragmentManager.moveToState(FragmentManager.java:1424)
at androidx.fragment.app.FragmentManager.dispatchStateChange(FragmentManager.java:2968)
at androidx.fragment.app.FragmentManager.dispatchStart(FragmentManager.java:2893)
at androidx.fragment.app.Fragment.performStart(Fragment.java:3171)
at androidx.fragment.app.FragmentStateManager.start(FragmentStateManager.java:588)
at androidx.fragment.app.FragmentStateManager.moveToExpectedState(FragmentStateManager.java:279)
at androidx.fragment.app.FragmentStore.moveToExpectedState(FragmentStore.java:113)
at androidx.fragment.app.FragmentManager.moveToState(FragmentManager.java:1424)
at androidx.fragment.app.FragmentManager.dispatchStateChange(FragmentManager.java:2968)
at androidx.fragment.app.FragmentManager.dispatchStart(FragmentManager.java:2893)
at androidx.fragment.app.FragmentController.dispatchStart(FragmentController.java:274)
at androidx.fragment.app.FragmentActivity.onStart(FragmentActivity.java:359)
How can I fix it?
try replacing parentFragmentManager with childFragmentManager
Related
I create ViewModel class at fragment, but viewModel is not saving after rotation - every time i got new Viewmodel instance. Where is problem?
Acrivity:
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
supportFragmentManager.beginTransaction()
.addToBackStack(null)
.replace(R.id.main_container, VideoFragment())
.commit()
}
}
Fragment:
class VideoFragment: Fragment() {
lateinit var viewModel: VideoViewModel
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
viewModel = ViewModelProvider(this).get(VideoViewModel::class.java)
return inflater.inflate(R.layout.fragment_video, container, false)
}
}
ViewModel:
class VideoViewModel: ViewModel() {
init {
Log.i("XXX", "$this ")
}
}
if i will use "requireActivity()" - as ViewModelStoreOwner - viewModel isnt recreate, but its will bound to activity lifecycle.
viewModel = ViewModelProvider(requireActivity()).get(VideoViewModel::class.java)
This is because you are replacing your Fragment on every configuration change when the Activity is recreated. The FragmentManager already retains your Fragment for you. You should commit the transaction only on the initial creation:
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
if (savedInstanceState == null) {
supportFragmentManager.beginTransaction()
.addToBackStack(null)
.replace(R.id.main_container, VideoFragment())
.commit()
}
}
}
Your problem is most likely caused due to the activity being destroyed and then created again after app is rotated.
To fix this you can give your fragment an id/tag when navigating and then when activity is rotated call your supportFragmentManager if there already exists an instance of your old fragment, if it does, navigate to old fragment instance, otherwise create a new instance just like you are doing now.
Wax911 (commented on 9 may 2020) answer to this question:
https://github.com/InsertKoinIO/koin/issues/693
He explains the problem with activities and their lifecycle when rotating the screen
In my fragment, I use viewlifecycleowner to observe live data but in some cases my app crashes with this log:
Fatal Exception: java.lang.IllegalStateException: Can't access the Fragment View's LifecycleOwner when getView() is null i.e., before onCreateView() or after onDestroyView()
So I ask how to reproduce and avoid this issue please!
You should register your LiveData observer in Fragment's onViewCreated. where viewlifecycleowner can never be null.
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
viewModel.someLiveData.observe(viewLifecycleOwner, Observer<Something> {
// Update the UI.
})
}
i have Fragment that include ImgaeView(s) in it's XML and i'm navigating from these images to another fragments but the problem is that the bottomsheet stays open, how to make it collapse when i navigate to another fragment?
here is a picture of the bottomsheet
and here i navigated to another fragment but the bottomsheet still appears on the screen
here is the code for inside the fragment
class MoreFragment : BottomSheetDialogFragment() {
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
// Inflate the layout for this fragment
return inflater.inflate(R.layout.fragment_more, container, false)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
home_button.setOnClickListener{
val homeFragment = HomeFragment()
activity?.supportFragmentManager?.beginTransaction()
?.replace(R.id.nav_host_fragment, homeFragment, "findThisFragment")
?.addToBackStack(null)
?.commit()
}
so my question is:
how to make it collapse down after i navigate to another fragment?
in Onclick listener before navigating to another fragment call dismiss() function
Just remove .addToBackStack(null) and call method dismiss()
Example of what i'm trying to achieve https://i.stack.imgur.com/lhp10.png
I'm stuck on the part where i need to switch to createUserFragment from defaultFragment
i attached this to my button, but nothing happens when i press it, not sure what's wrong there
MainActivity
class MainActivity : AppCompatActivity() {
lateinit var appBarConfiguration: AppBarConfiguration
lateinit var navController: NavController
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
navController = findNavController(R.id.hostFragment)
appBarConfiguration = AppBarConfiguration(navController.graph,drawer_layout)
NavigationUI.setupActionBarWithNavController(this, navController,drawer_layout)
NavigationUI.setupWithNavController(navigation_drawer,navController)
}
}
override fun onSupportNavigateUp(): Boolean {
return NavigationUI.navigateUp(navController,appBarConfiguration)
}
defaultFragment
class defaultFragment : Fragment() {
private lateinit var binding: defaultFragmentBinding
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
// Inflate the layout for this fragment
binding = defaultFragmentBinding.inflate(inflater)
return (binding.root)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
binding.createNewBlankFAB.setOnClickListener{
val transaction = (activity as MainActivity).supportFragmentManager.beginTransaction()
transaction.replace(R.id.defaultFragment, createUserFragment())
transaction.disallowAddToBackStack()
transaction.commit()
}
}
}
My questions are:
1)How can i fix my thing?
2)Will it work properly? I.e no memory leaks or some other funky stuff
3)Do i have to use another fragment for data input or maybe there's another way?
UPDATE
I stil have no idea how this works, but apparently i was replacing wrong fragment, i switched this R.id.defaultFragment in transaction.replace to R.id.hostFragment from which, i assume, all other fragments spawn, but now it just spawn on top of my existing fragment and the drawer button doesn't change its state, i guess i have to either do all of this differently or somehow pass to the drawer navigation information that current fragment was changed?
This is how we navigate within fragments in Navigation component library . We use navigate to then id of the destination Fragment which is defined in navgraph
binding.createNewBlankFAB.setOnClickListener{
Navigation.findNavController(view).navigate(R.id.createUserFragment);
}
I think you have to call "transaction.add" instead of "replace" since you are calling your fragment from an activity. Replace is called when you call a fragment from another fragment, I believe.
I want to go from FragmentA (RootFragment) to FragmentB but I do not want to recreate the view of FragmentA once it comes back from FragmentB.
I am using Jetpack Navigation for navigating between Fragments.
To achieve the above goal, I have a fragment Fragment like this:
class RootFragment : DaggerFragment() {
private var viewToRestore: View? = null
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
return if (viewToRestore != null) {
viewToRestore!!
} else {
return inflater.inflate(R.layout.fragment_root, parent, false)
}
}
override fun onDestroyView() {
viewToRestore = this.view
super.onDestroyView()
}
override fun onDestroy() {
super.onDestroy()
}
}
But the FragmentA (RootFragment) is leaking once I reach Fragment B with the attribute viewToRestore.
Any solution that can work without leak but achieve the same goal?
The leak is a false positive. It is totally fine from a Fragment point of view to hold onto the View you create in onCreateView and return it later, under the condition that your Fragment is not retained or otherwise kept longer than the Context used to create the view is alive.
The problem that you have is a problem for Jetpack navigation as you cannot add when going to another fragment you can just replace:
supportFragmentManager.beginTransaction()
.replace(R.id.fragment_container, FragmentB.newInstance())
.addToBackStack(null)
.commit()
supportFragmentManager.beginTransaction()
.add(R.id.fragment_container, FragmentB.newInstance())
.addToBackStack(null)
.commit()
This is the difference replace and add.
I searched a lot and I guess that jetPack navigation does not support add instead of replace, so I recommend not to use navigation when it's important not to recreate the first fragment