I have the following code snippets
Please help me figure out the difference between the same
Snippet 1
navbar.setOnItemSelectedListener { id ->
var fragment: Fragment? = null
when (id) {
R.id.home -> fragment = HomeFragment()
R.id.graphical_stats -> fragment = GraphicalStats()
R.id.sources -> fragment = SourcesFragment()
}
if (fragment != null) {
supportFragmentManager.beginTransaction().replace(R.id.container_frame, fragment)
.commit()
} else {
Log.e(TAG, "Error Creating Fragment")
}
}
Snippet 2
navbar.setOnItemSelectedListener { object : ChipNavigationBar.OnItemSelectedListener{
override fun onItemSelected(id: Int) {
var fragment: Fragment? = null
when (id) {
R.id.home -> fragment = HomeFragment()
R.id.graphical_stats -> fragment = GraphicalStats()
R.id.sources -> fragment = SourcesFragment()
}
if (fragment != null) {
supportFragmentManager.beginTransaction().replace(R.id.container_frame, fragment)
.commit()
} else {
Log.e(TAG, "Error Creating Fragment")
}
}
I am using ChipNavigationBar and have three fragments namely Home Graphical Stats and Sources which will be created or swapped accordingly
Functionality wise, both the snippets are the same.
Snippet 1 is using a lambda expression for setOnItemSelectedListener whereas the Snippet 2 uses a singleton object implementing the interface ChipNavigationBar.OnItemSelectedListener.
To simply your code, you should use a lambda expression.
Also, you can use lambda expression only if your interface has only 1 function that's need to overridden.
I use a BottomNavigationView with four pages. If I want to add 4 fragments to that it is Ok but when I want to replace new fragment with old fragment in one of the BottomNavigationView page and restore it when item click, the first fragment open again. How can I restore the last fragment? Should I use different FrameLayout in the first BottomNavigationView?
Try this:
bottomNavView.setOnNavigationItemSelectedListener(object : BottomNavigationView.OnNavigationItemSelectedListener {
override fun onNavigationItemSelected(item: MenuItem): Boolean {
clearAllBackStack()
when (item.itemId) {
R.id.itemHome -> {
// load fragment here
}
R.id.itemMyAccount -> {
// load fragment here
}
R.id.itemSettings -> {
// load fragment here
}
R.id.itemNotfications -> {
// load fragment here
}
}
return true
}
})
add this method:
fun clearAllBackStack() {
for (i in 0 until supportFragmentManager.backStackEntryCount) {
supportFragmentManager.popBackStack()
}
}
Note: also add fragment to backstack whenever you load fragment, add this when you load fragment: fragmentTransaction.addToBackStack(null)
I want to resume android fragments that are in the backstack. When switching between different tabs on bottomnavigation,i don't want the views of fragments to be recreated.
I read this, this,this and some other questions on stackoverflow related to this. Common suggestions are to use show and hide method of fragment transactions but it is not working. Here is my kotlin code:
bottomnavigation.setOnNavigationItemSelectedListener {
item ->
when(item.itemId){
R.id.first_fragment_item -> {
var fragment:Fragment = FirstFragment.newInstance()
replaceFragment(fragment)
return#setOnNavigationItemSelectedListener true
}
R.id.second_fragment_item -> {
var fragment:Fragment = SecondFragment.newInstance()
replaceFragment(fragment)
return#setOnNavigationItemSelectedListener true
}
}
return#setOnNavigationItemSelectedListener false
}
}
fun replaceFragment(fragment:Fragment) {
var fragmentName:String = fragment::class.simpleName!!
if(supportFragmentManager.findFragmentByTag(fragmentName)==null) {
val fragmentTransaction = supportFragmentManager.beginTransaction()
fragmentTransaction.replace(R.id.fragment_container, fragment, fragmentName)
fragmentTransaction.addToBackStack(fragmentName)
fragmentTransaction.commit()
}
else{
if(fragmentName == "FirstFragment") {
supportFragmentManager.beginTransaction().
hide(supportFragmentManager.findFragmentByTag("SecondFragment"))
.show(supportFragmentManager.findFragmentByTag("FirstFragment"))
.commit()
}
else{
supportFragmentManager.beginTransaction()
.hide(supportFragmentManager.findFragmentByTag("FirstFragment"))
.show(supportFragmentManager.findFragmentByTag("SecondFragment"))
.commit()
}
}
}
The last fragment of backstack is always shown. When i want to hide it and show
the other fragment, that fragment screen becomes white with nothing on it.
My bottomnavigation has four items but for testing purpose i am only using two. Two of fragments are FirstFragment and SecondFragment. I am using v4.app.fragments.
With the help of Kishan Viramgama comment's, i solved my problem. I used "add" method instead of "replace". This is my code for resuming fragments: `
fun replaceFragment(fragment: Fragment) {
var nameOfFragment: String? = fragment::class.simpleName
var fragmentTransaction = supportFragmentManager.beginTransaction()
var fm: FragmentManager = supportFragmentManager
if (supportFragmentManager.findFragmentByTag(nameOfFragment) == null) {
fragmentTransaction.add(R.id.root_fragments, fragment, nameOfFragment) //if fragment is not added to the backstack,add it/ don't use replace because it creates new fragment emptying fragment container
fragmentTransaction.addToBackStack(nameOfFragment)
fragmentTransaction.commit()
} else {
var fragmentToShow:Fragment = supportFragmentManager.findFragmentByTag(nameOfFragment) //if fragment is already in backstack,show it and hide all other fragments.
for (i in 0 until fm.backStackEntryCount) {
var fragmentToHide: Fragment = fm.findFragmentByTag(fm.getBackStackEntryAt(i).name)
if (fragmentToHide::class.simpleName != fragment::class.simpleName)
fragmentTransaction.hide(fragmentToHide)
}
fragmentTransaction.show(fragmentToShow)
fragmentTransaction.commit()
}
}
`
I am using a BottomNavigationView for a Tab-Interface in my main activity. in onCreate(), the switchTab method is called with the initial fragment. Tapping the the bottom navigation calls switchTab() for the respective tabs and should hide the current and display the new one. If the fragment was not added to the SupportFragmentManager, it gets added, otherwise it will be shown. Here's my code snippet:
private fun switchTab(fragment: Fragment, tag: String): Boolean {
val currentFragment = supportFragmentManager.fragments.find { it.tag == tag }
val ta = supportFragmentManager.beginTransaction()
if (currentFragment != null) {
ta.hide(currentFragment)
}
if (supportFragmentManager.fragments.contains(fragment)) {
ta.show(fragment)
} else {
ta.add(R.id.contentContainer, fragment, tag)
}
ta.commit()
return true
}
Now the problem is that sometimes two fragments are visible and overlay each other, making the userinterface unusable. How can this happen?
I have started to study Kotlin and do not know all the functionality of the language.
The function is used to show the fragment in the FrameLayout. The logic is so that the first time it should always add() the fragment and next times it will replace(). Also in some cases I need to use addToBackStack() and also in the same situations to disable the left-side menu.
fun showFragment(fragment : Fragment,
isReplace: Boolean = true,
backStackTag: String? = null,
isEnabled: Boolean = true)
{
/* Defining fragment transaction */
val fragmentTransaction = supportFragmentManager
.beginTransaction()
/* Select if to replace or add a fragment */
if(isReplace)
fragmentTransaction.replace(R.id.frameLayoutContent, fragment, backStackTag)
else
fragmentTransaction.add(R.id.frameLayoutContent, fragment)
/* Select if to add to back stack */
if(backStackTag != null)
fragmentTransaction.addToBackStack(fragment.javaClass.name)
fragmentTransaction.commit()
enableDrawer(isEnabled)
}
Question: Are there some possible improvements of the function code related to the specifications of Kotlin language to make code cleaner as for now the function looks as a mass.
I have posted a blog about the below answer.
I will write an Extension function to the FragmentManager which accepts a Lambda with Receiver as an argument.
inline fun FragmentManager.inTransaction(func: FragmentTransaction.() -> Unit) {
val fragmentTransaction = beginTransaction()
fragmentTransaction.func()
fragmentTransaction.commit()
}
To add a fragment, we can call like this from the Activity now:
supportFragmentManager.inTransaction {
add(R.id.frameLayoutContent, fragment)
}
The advantage of this is we don't have to call beginTransaction() and commit() every time we add or replace a Fragment now. How many hours have you wasted debugging only to find out that you have missed calling commit() in Java?
Next, I will write Extension functions to AppCompatActivity:
fun AppCompatActivity.addFragment(fragment: Fragment, frameId: Int, backStackTag: String? = null) {
supportFragmentManager.inTransaction {
add(frameId, fragment)
backStackTag?.let { addToBackStack(fragment.javaClass.name) }
}
}
fun AppCompatActivity.replaceFragment(fragment: Fragment, frameId: Int, backStackTag: String? = null) {
supportFragmentManager.inTransaction {
replace(frameId, fragment)
backStackTag?.let { addToBackStack(fragment.javaClass.name) }
}
}
So that now we can add/ replace Fragment from any Activity in a single line, without any additional qualifiers:
addFragment(yourFragment, R.id.frameLayoutContent, "tag")
replaceFragment(yourFragment, R.id.frameLayoutContent, "tag")
I like to use with and let when I have some code related to using a lot some variable and null checks
So I'd do something like this:
fun showFragment(fragment : Fragment,
isReplace: Boolean = true,
backStackTag: String? = null,
isEnabled: Boolean = true)
{
/* Defining fragment transaction */
with(supportFragmentManager.beginTransaction()) {
/* Select if to replace or add a fragment */
if(isReplace)
replace(R.id.frameLayoutContent, fragment, backStackTag)
else
add(R.id.frameLayoutContent, fragment)
/* Select if to add to back stack */
backStackTag?.let { addToBackStack(it) }
commit()
}
enableDrawer(isEnabled)
}
Everything is fine. Probably here
if(backStackTag != null)
fragmentTransaction.addToBackStack(fragment.javaClass.name)
you want to add fragment using backStackTag like this
if(backStackTag != null)
fragmentTransaction.addToBackStack(backStackTag)
You can create an extension function like this:
Import android.support.v4.app.FragmentManager and android.support.v4.app.FragmentTransaction if you wish to make your app compatible with devices running system versions as low as Android 1.6 using supportFragmentManager(which uses the support library) instead of fragmentManager.
Define the following function on the top level, i.e. directly under the package:
inline fun FragmentManager.inTransaction(func: FragmentTransaction.() Unit) {
val fragmentTransaction = beginTransaction()
fragmentTransaction.func()
fragmentTransaction.commit()
}
Call the function from any activity:
supportFragmentManager.inTransaction {
add(R.id.frameLayoutContent, fragment)
}
You can use below code for replace:
fun Fragment.moveAnotherFragment(fragment: Fragment,addToBackStack: Boolean = true, func : Bundle?. () -> Unit = {}) {
fragment.enterTransition =
activity?.supportFragmentManager?.beginTransaction()?.apply {
replace(
R.id.frame, fragment.apply {
enterTransition = Slide(Gravity.END)
exitTransition = Slide(Gravity.START)
arguments.apply(func)
}
)
if (addToBackStack) addToBackStack(null)
commit()
}
}