I have an activity initializing FragmentA and FragmentA contains FragmentB.
So when I replace FragmentA with some FragmentC, the onPause() and onResume() function of FragmentB is not getting called.
And, when I return back to FragmentA, FragmentB's onPause() is getting called before onResume().
Here is my activity sample code :
class MyActivity : Activity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
if (savedInstanceState == null) {
val myFragmentA = FragmentA()
fragmentManager
.beginTransaction()
.replace(R.id.fragContainer, myFragmentA, FRAGMENT_A_TAG)
.commit()
}
}
}
Here is my Fragment sample code :
class FragmentA : Fragment() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// loading fragmentB
fragmentManager.beginTransaction().replace(R.id.fragBContainer, FragmentB()).commit()
}
override fun onViewCreated(view: View?, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
//my layout changes
}
}
Fragment B : -
class FragmentB : Fragment() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
//Todo -- empty
}
override fun onViewCreated(view: View?, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
//my layout changes
}
}
The reason that Fragment B isn't calling it's onResume or onPause() is because it is not being added to the view properly. If you want to nest fragments you need to use getChildFragmentManager() instead of the regular FragmentManager() when managing the nested fragments:
getChildFragmentManager().beginTransaction().replace(R.id.fragBContainer, FragmentB()).commit()
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
I have 2 fragments that i navigate throught navigation component from Jetpack. When i go back from fragment 2 to fragment 1, i fire an event throught livedata from fragment 2 and fragment1 should be observing. Althought, fragment 1 didnt observed and when i sste a breakpoint to check how many observers liveData has, it say 0.
Code below
class Fragment1 :
Fragment() {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
myViewModel.selectedItem.observe(viewLifecycleOwner, {
println("Observed")
})
findNavController().navigate(
Fragment1Directions.actionToFragment2()
)
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
myViewModel = ViewModelProvider(requireActivity()).get(MyViewModel::class.java)
}
private lateinit var myViewModel: MyViewModel
}
class Fragment2 :
Fragment() {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
myButton.setOnClickListener{
MyViewModel().selectItem(true)
findNavController().navigateUp()
}
}
}
class MyViewModel : ViewModel() {
private val mutableSelectedItem = MutableLiveData<Boolean>()
val selectedItem: LiveData<Boolean> get() = mutableSelectedItem
fun selectItem(value: Boolean) {
mutableSelectedItem.value = value
}
}
It's strange to not have observer for
myViewModel.selectedItem.observe(viewLifecycleOwner, {
println("Observed")
})
when it's called onViewCreated and with viewLifecycleOwner, but it looks like you have a mistake for creating common ViewModel for Fragment1 and Fragment2
Both should use
myViewModel = ViewModelProvider(requireActivity()).get(MyViewModel::class.java) to get same ViewModel for both fragments
I need to call a fragment method in the activity....
FirstFragment.kt
class FirstFragment: Fragment() {
fun getToast(context: Context) {
return Toast.makeText(context, "hello", Toast.LENGTH_SHORT).show()
}
}
MainActivity.kt
class MainActivity: AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val transaction = supportFragmentManager.beginTransaction()
transaction.replace(R.id.fragment_container, FirstFragment, FistFragmentTAG_NAME)
transaction.commit()
//expecting to use fragment method...
parameter.getToast() //parameter is the code neede to call FirstFragment method .getToast()
}
}
Ive tried these 3 cases without success
Ive tried: (it crashes the app)
FirstFragment().getToast(this#MainActivity)
Also tried, but the function .getToast() appears red
val myToast = supportFragmentManager.findFragmentByTag(FistFragmentTAG_NAME).getToast(this#MainActivity)
Also tried, but the funtion .getToast() appears red as well
supportFragmentManager.fragments.forEach { it: Fragment
val getToast = it.getToast(this#MainActivity)
the problem here is not the context, because in the real, Im trying to call a method that doesnt use context at all... Context was set in this example just to call a Toast.
How do I call the fragment method??*
This is activity class
class MyActivity : AppCompatActivity() {
private lateinit var myFragment: FirstFragment
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
myFragment = FirstFragment()
supportFragmentManager
.beginTransaction()
.add(R.id.fragment_container, myFragment , "MyFragment")
.commit()
//this is fragment method, we call it from activity
myFragment.getToast(this)
}
}
This is fragment class
class FirstFragment: Fragment() {
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
return super.onCreateView(inflater, container, savedInstanceState)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
}
fun getToast(context: Context) {
Toast.makeText(context, "Hello", Toast.LENGTH_SHORT).show()
}
}
I tried to find an easy way to listen to fragment changes from my activity in order to hide/show the drawer menu button from my LoginFragment and I could not find a good and easy way to implement for my case here in sfo, so I would like to share an easy solution I eventually came up with using ViewModel and a LiveData which saves the fragment class name that is currently displayed and observing it from the activity to listen for changes.
NOTE the solution works in case that your fragments are displayed on the same FragmentContainerView in your layout
Here is an exmaple:
ViewModel class :
class MyViewModel : ViewModel(){
val currentFragment = MutableLiveData<String>()
}
Now set currentFragment value inside your fragments:
class LoginFragment() : Fragment() {
private lateinit var : viewModel : MyViewModel
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
ViewModel = ViewModelProvider(requireActivity()).get(MyViewModel::class.java)
ViewModel.currentFragment.value = this::class.java.name
}
}
class MainFragment() : Fragment() {
private lateinit var : viewModel : MyViewModel
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
ViewModel = ViewModelProvider(requireActivity()).get(MyViewModel::class.java)
ViewModel.currentFragment.value = this::class.java.name
}
}
Now in your Activity you can observe currentFragment and do whatever you want(in my case I wanted to know if the current fragment is LoginFragment and hide the drawer menu button from the toolbar) :
class MainActivity() : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
mapViewModel.currentFragment.observe(this, {
when (it) {
LoginFragment::class.java.name -> {
//your stuff related to LoginFragment
}
MainFragment::class.java.name -> {
//your stuff related to MainFragment
}
}
})
}
}
Hope this helps anyone ^^
in FragmentA() I get data from the previously Fragment
class FragmentA():Fragment() {
private lateinit var personList: MutableList<Person>
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
arguments?.let {
personList = it.getParcelableArrayList<Person>("person") as MutableList<Person>
}
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
if(personList.isEmpty()){
showEmptyContainer()
}else{
recyclerAdapter.setItems(personList)
}
}
Now, this code works when I open FragmentA(), but now, if I go from this fragment to FragmentB() and come back, my data is duplicated. So, I tried cleaning the array and set it up again
class FragmentA():Fragment() {
private lateinit var personList: MutableList<Person>
private var backupList:MutableList<Person> = mutableListOf()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
arguments?.let {
personList = it.getParcelableArrayList<Person>("person") as MutableList<Person>
}
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
if(personList.isEmpty()){
showEmptyContainer()
}else{
backupList.clear()
backupList = personList
recyclerAdapter.setItems(backupList)
}
}
Doing this, it works when coming back, but, for some reason it shows the empty container when I do this twice so my question is
How can I retain this fragment personList while navigation forward and pressing the back button ?
Thanks
You can use onSaveInstance and then check in onCreate if your savedInstanceState isn't null, here is a topic that will help you set it.
onSaveInstanceState () and onRestoreInstanceState ()