Cannot remove Fragment in Android - android

I have two fragments, named them "Red" and "Blue" and three buttons. The first one adds "Red" fragment and changes the background to red color, the second one adds "Blue" fragment and changes the background to blue color and the third one has to remove the current fragment and change the color to the previous fragment's color.
The problem is that when I click the remove button I see a toast that the fragment is removed but the layout doesn't change to the color of the previous fragment.
var fragment1 = FirstFragment()
var fragment2 = SecondFragment()
lateinit var binding: ActivityMainBinding
var counter: Int = 0
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
binding.redBtn.setOnClickListener {
supportFragmentManager
.beginTransaction()
.add(R.id.placeHolder, fragment1)
.addToBackStack("Red")
.commit()
counter++
binding.count.text = "$counter"
}
binding.blueBtn.setOnClickListener {
supportFragmentManager
.beginTransaction()
.replace(R.id.placeHolder, fragment2)
.addToBackStack("Blue")
.commit()
counter++
binding.count.text = "$counter"
}
binding.removeBtn.setOnClickListener {
val fragmentInstance = supportFragmentManager.findFragmentById(R.id.placeHolder)
if(fragmentInstance is FirstFragment){
supportFragmentManager.beginTransaction()
.remove(FirstFragment())
.commit()
Toast.makeText(this, "Removed Red Fragment", Toast.LENGTH_SHORT).show()
} else {
supportFragmentManager.beginTransaction()
.remove(SecondFragment())
.commit()
Toast.makeText(this, "Removed Blue Fragment", Toast.LENGTH_SHORT).show()
}
counter--
binding.count.text = "$counter"
}
}
}

It will not remove directly
make sure create object of First and Second Fragment
Then when you add or remove fragment from fragment manager use the same fragment object
Like you created
fragment1 and fragment2

Hello i think you have just to pass the framgents objects that aleady createed to be removed like this:
binding.removeBtn.setOnClickListener {
val fragmentInstance = supportFragmentManager.findFragmentById(R.id.placeHolder)
if(fragmentInstance is FirstFragment){
supportFragmentManager.beginTransaction()
.remove(fragment1)
.commit()
Toast.makeText(this, "Removed Red Fragment", Toast.LENGTH_SHORT).show()
} else {
supportFragmentManager.beginTransaction()
.remove(fragment2)
.commit()
Toast.makeText(this, "Removed Blue Fragment", Toast.LENGTH_SHORT).show()
}
counter--
binding.count.text = "$counter"
}
your problem is every click on remove btn will create new instance of fragment not the object already created

Related

How can I start an activity with specific fragment

In my main activity I have bottom navigation bar. Each button opens a different fragment.My code in main activity looks like this
class MainActivity : AppCompatActivity() {
private val homeFragment = HomeFragment()
private val calendarFragment = CalendarFragment()
private val addFragment = AddFragment()
private val plannerFragment = PlannerFragment()
private val profileFragment = ProfileFragment()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
replaceFragment(homeFragment)
nav_view.setOnNavigationItemSelectedListener{
when(it.itemId){
R.id.homeButton -> replaceFragment(homeFragment)
R.id.calendarButton -> replaceFragment(calendarFragment)
R.id.addButton -> replaceFragment(addFragment)
R.id.plannerButton -> replaceFragment(plannerFragment)
R.id.profileButton -> replaceFragment(profileFragment)
}
true
}
}
private fun replaceFragment(fragment: Fragment){
if (fragment!=null){
val transaction = supportFragmentManager.beginTransaction()
transaction.replace(R.id.fragmentContainer, fragment)
transaction.commit()
}
}
}
In profileFragment I have a button that opens a new activity called EditProfile. In that activity I have a button called goBackToProfileButton
I want to set a listener that will go back to mainactivity, but I want profileFragment to be open not the default fragment which is homeFragment.
goBackToProfileButton.setOnClickListener {
val intent = Intent(this,MainActivity::class.java)
startActivity(intent)
}
For now my code looks like this
You need to add some bundle data to your intent with information which fragment should be started
https://stackoverflow.com/a/819427/11538132
And then when you receive this bundle data then you can manually set your ** profileFragment**
You can add TAG while fragment transaction.
private fun replaceFragment(fragment: Fragment){
if (fragment!=null){
val transaction = supportFragmentManager.beginTransaction()
transaction.replace(R.id.fragmentContainer, fragment, "FragmentTag")
transaction.commit()
}
}
While calling the first time to replace a fragment, find your fragment by tag.
//replaceFragment(homeFragment)
val fragment = supportFragmentManager.findFragmentByTag("FragmentTag")
if(fragment !=null){
replaceFragment(fragment)
} else{
replaceFragment(homeFragment)
}
if findFragmentByTag return null means no fragment was added. So add Home Fragment. If not null , it will update with last fragment added.
You could use fragment.startActivityForResult() and call the ProfileFragment from the MainActivity once it receives the onActivityResult() callback.

Transaction from one Fragment to another with beginTransaction()

I'm pretending to switch from one Fragment to another and get a saved Bundle configuration between them:
Fragment A (named SpotSelection)
// Communication
interface OnMessageSendListener {
public fun onMessageSend(id: Int, currentFragment: Fragment)
}
var messageSendListener: OnMessageSendListener? = null
override fun onAttach(context: Context) {
super.onAttach(context)
val activity: Activity = context as Activity
try {
messageSendListener = activity as OnMessageSendListener
} catch (e: ClassCastException) {
throw ClassCastException(activity.toString() + " must implement onMessageSendListener...")
}
}
[...]
// This is a method of a setOnClickListener button, to switch from the fragment "SpotSelection" to the fragment "Spots" when I press the button
private fun entrarColeccion(id: Int) {
Log.d("selection", "Entrando en onMessageSend(" + id + ")")
getVisibleFragment()?.let { messageSendListener?.onMessageSend(id, it) }
// I cannot do this because I would miss the Bundle ;( it would be so easy...
//findNavController().navigate(R.id.action_spotSelection_to_spots)
}
fun getVisibleFragment(): Fragment? {
val fragmentManager: FragmentManager = requireActivity().supportFragmentManager
val fragments: List<Fragment> = fragmentManager.getFragments()
if (fragments != null) {
for (fragment in fragments) {
if (fragment != null && fragment.isVisible) return fragment
}
}
return null
}
MainActivity
// SpotSelection Communication
override fun onMessageSend(id: Int, currentFragment: Fragment) {
Log.d("selection", "Dentro de onMessageSend(" + id + ")")
// The Fragment where I want to go
val spotsFragment: Spots = Spots()
val bundle: Bundle = bundleOf(Pair("coleccionID", id))
Log.d("selection", "Guardando bundle: (coleccionID, " + id + ")")
spotsFragment.setArguments(bundle)
supportFragmentManager.beginTransaction()
.hide(currentFragment)
.replace(R.id.fragment_container, spotsFragment)
.show(spotsFragment)
.addToBackStack(null)
.commit()
}
It does the following (by my observations of the debbuger): hides the actual fragment (SpotSelection), start the onCreateView of the Spots fragment (where I want to go), but doesn't display its UI. How do I display the UI of Spots? I feel like I'm so close...
using replace may cause problem some times use this maybe help you:
FragmentManager fragmentManager = getSupportFragmentManager();
Fragment fragment = fragmentManager.findFragmentById(R.id.fragment_container);
if(fragment == null){
fragmentManager.beginTransaction()
.hide(currentFragment)
.add(R.id.fragment_container, spotsFragment)
.show(spotsFragment)
.addToBackStack(null)
.commit()
}else{
fragmentManager.beginTransaction()
.hide(currentFragment)
.replace(R.id.fragment_container, spotsFragment)
.show(spotsFragment)
.addToBackStack(null)
.commit()
}

How can I hide a fragment using supportFragmentManager?

Here's my attempt:
private inline fun FragmentManager.inTransaction(func: FragmentTransaction.() -> FragmentTransaction) {
beginTransaction().func().addToBackStack(null).commit()
}
private fun AppCompatActivity.addFragment(fragment: Fragment, frameId: Int){
supportFragmentManager.inTransaction { add(frameId, fragment) }
}
private fun AppCompatActivity.showFragment(fragment: Fragment) {
supportFragmentManager.inTransaction{show(fragment)}
}
private fun showFragmentView(fragment: Fragment){
// Hide the current Fragment
if (supportFragmentManager.fragments.isNotEmpty()) {
val currentFragment = supportFragmentManager.fragments.last()
if (currentFragment != null) {
supportFragmentManager
.beginTransaction()
.hide(currentFragment)
.commit()
}
}
// Add or Show
if (!fragment.isAdded) {
addFragment(fragment, sendFragFrame.id)
} else {
showFragment(fragment)
}
}
It properly adds the fragment to the frame, but when I attempt to hide it nothing happens, it's stays visible and the second fragment cannot be seen. Can someone explain why this is happening?
It is not good practice (and most likely won't work) to access fragments in this way.
if (supportFragmentManager.fragments.isNotEmpty()) {
val currentFragment = supportFragmentManager.fragments.last()
if (currentFragment != null) {
supportFragmentManager
.beginTransaction()
.hide(currentFragment)
.commit()
You should add fragments with a tag and get the fragment by tag when you want to remove it and then do your transaction.
See the comments on this for more:
How do I get the currently displayed fragment?
Well embarrassingly my problem was that I had inadvertently swap my layout in Fragment2 with the layout for Fragment1...so it WAS working, but because they shared a layout you couldn't see it visually. I'd delete this post if I could, but I'll leave it here as tribute to my shame as a developer.

How to insert a fragment layout within a Frame Layout

I'm trying to create an Android app using Kotlin for the first time and am trying to use different fragment layouts when the user clicks on navigation icons without having them overlap my bottom navigation bar. I've created a FrameLayout with the intention of putting the fragment layouts within it, but am unclear on how to write the code to do so.
This is the MainActivity.kt that I have that covers the Nav bar now:
private val onNavigationItemSelectedListener = BottomNavigationView.OnNavigationItemSelectedListener { item ->
when (item.itemId) {
R.id.navigation_home -> {
setContentView(R.layout.fragment_home)
return#OnNavigationItemSelectedListener true
}
R.id.navigation_favorites -> {
setContentView(R.layout.fragment_favorite)
return#OnNavigationItemSelectedListener true
}
R.id.navigation_calculator -> {
setContentView(R.layout.fragment_calculator)
return#OnNavigationItemSelectedListener true
}
}
false
}
And this is my frame layout:
<FrameLayout
android:id="#+id/fragment_container"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_above= "#id/nav_view">
</FrameLayout>
You want to navigate to the appropriate fragment when a tab on the NavBar is selected. This is how you do it.
Let's say we have a MainActivity which has 3 tabs: "Tab1", "Tab2", "Tab3"
The MainActivity will have 2 XML Layouts: The navBar and the FrameLayout.
Then, you're going to want to create 3 new Fragments (each having their own XML layout too). Those 3 Fragments will take the view of the framelayout when the appropriate navbar tab is selected
For Example:
MAIN ACTIVITY XML
<FrameLayout
android:id="#+id/mainFragment"
android:layout_width="match_parent"
android:background="#ffffff"
android:layout_height="match_parent"
android:layout_above="#+id/bottom_nav"
android:text="#string/title_home" />
<com.google.android.material.bottomnavigation.BottomNavigationView
android:id="#+id/bottom_nav"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:background="?android:attr/windowBackground"
app:menu="#menu/bottom_navigation" />
MAIN ACTIVITY KOTLIN
class MainActivity : AppCompatActivity() {
private lateinit var manager: FragmentManager
private lateinit var transaction: FragmentTransaction
private val mOnNavigationItemSelectedListener = BottomNavigationView.OnNavigationItemSelectedListener { item ->
when (item.itemId) {
R.id.navigation_tab1 -> {
title = "Tab1"
manager = supportFragmentManager
transaction = manager.beginTransaction()
transaction.replace(R.id.mainFragment, Tab1Fragment())
transaction.addToBackStack(null)
transaction.commit()
return#OnNavigationItemSelectedListener true
}
R.id.navigation_tab2 -> {
title = "Tab2"
manager = supportFragmentManager
transaction = manager.beginTransaction()
transaction.replace(R.id.mainFragment, Tab2Fragment())
transaction.addToBackStack(null)
transaction.commit()
return#OnNavigationItemSelectedListener true
}
R.id.navigation_tab3 -> {
title = "Tab 3"
manager = supportFragmentManager
transaction = manager.beginTransaction()
transaction.replace(R.id.mainFragment, Tab3Fragment())
transaction.addToBackStack(null)
transaction.commit()
return#OnNavigationItemSelectedListener true
}
}
false
}
#SuppressLint("CommitTransaction")
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val navigation = findViewById<View>(R.id.bottom_nav) as BottomNavigationView
navigation.setOnNavigationItemSelectedListener(mOnNavigationItemSelectedListener)
}
}
Please refer to very good source of knowledge: https://developer.android.com/training/basics/fragments/fragment-ui#AddAtRuntime
basically you need to create transaction which is mentioned in link:
val transaction = supportFragmentManager.beginTransaction().apply {
// Replace whatever is in the fragment_container view with this fragment,
// and add the transaction to the back stack so the user can navigate back
replace(R.id.fragment_container, newFragment)
addToBackStack(null)
transaction.commit()
}
so in your case:
when (item.itemId) {
var fragment: Fragment
R.id.navigation_home -> {
fragment = HomeFragment()
}
.
.
.
val transaction = supportFragmentManager.beginTransaction().apply {
replace(R.id.fragment_container, fragment)
addToBackStack(null)
transaction.commit()
you can also use use add instead of replace.
Please refer to this post to determine which method use:
Difference between add(), replace(), and addToBackStack()

How do I call more than one function for each case when using a "when" statement in Kotlin?

I have a chain of 8 buttons I've positioned as a side menu, where on their right there is a fragment container. Clicking on each button opens a different fragment.
This is done with an onClick listener and a when statement.
What I want to do is that other than changing the fragment in the box, I want the background for each button to change when it is clicked, and return to default when another one is clicked. What is the best way to go around doing that?
Can I call multiple actions for each case in the when statement? Do I need for each button clicked to set its background and the background for the other 7? It seems like too much code.
class MainActivity : AppCompatActivity(), View.OnClickListener {
override fun onClick(v: View?) {
when (v?.id){
R.id.recipeOneButton -> createRecipeOne()
R.id.recipeTwoButton -> createRecipeTwo()
R.id.recipeThreeButton -> createRecipeThree()
R.id.recipeFourButton -> createRecipeFour()
R.id.recipeFiveButton -> createRecipeFive()
R.id.recipeSixButton -> createRecipeSix()
R.id.recipeSevenButton -> createRecipeSeven()
R.id.recipeEightButton -> createRecipeEight()
}
}
private val manager = this.supportFragmentManager!!
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
createRecipeOne()
val recipeOneButton : Button = findViewById(R.id.recipeOneButton)
val recipeTwoFragment : Button = findViewById(R.id.recipeTwoButton)
val recipeThreeButton : Button = findViewById(R.id.recipeThreeButton)
val recipeFourButton : Button = findViewById(R.id.recipeFourButton)
val recipeFiveButton : Button = findViewById(R.id.recipeFiveButton)
val recipeSixButton : Button = findViewById(R.id.recipeSixButton)
val recipeSevenButton: Button = findViewById(R.id.recipeSevenButton)
val recipeEightButton : Button = findViewById(R.id.recipeEightButton)
recipeOneButton.setOnClickListener(this)
recipeTwoFragment.setOnClickListener(this)
recipeThreeButton.setOnClickListener(this)
recipeFourButton.setOnClickListener(this)
recipeFiveButton.setOnClickListener(this)
recipeSixButton.setOnClickListener(this)
recipeSevenButton.setOnClickListener(this)
recipeEightButton.setOnClickListener(this)
}
fun createRecipeOne(){
val transaction = manager.beginTransaction()
val fragment = RecipeOne()
transaction.replace(R.id.fragmentHolder, fragment)
transaction.addToBackStack(null)
transaction.commit()
}
fun createRecipeTwo(){
val transaction = manager.beginTransaction()
val fragment = RecipeTwo()
transaction.replace(R.id.fragmentHolder, fragment)
transaction.addToBackStack(null)
transaction.commit()
}
fun createRecipeThree(){
val transaction = manager.beginTransaction()
val fragment = RecipeThree()
transaction.replace(R.id.fragmentHolder, fragment)
transaction.addToBackStack(null)
transaction.commit()
}
fun createRecipeFour(){
val transaction = manager.beginTransaction()
val fragment = RecipeFour()
transaction.replace(R.id.fragmentHolder, fragment)
transaction.addToBackStack(null)
transaction.commit()
}
fun createRecipeFive(){
val transaction = manager.beginTransaction()
val fragment = RecipeFive()
transaction.replace(R.id.fragmentHolder, fragment)
transaction.addToBackStack(null)
transaction.commit()
}
fun createRecipeSix(){
val transaction = manager.beginTransaction()
val fragment = RecipeSix()
transaction.replace(R.id.fragmentHolder, fragment)
transaction.addToBackStack(null)
transaction.commit()
}
fun createRecipeSeven(){
val transaction = manager.beginTransaction()
val fragment = RecipeSeven()
transaction.replace(R.id.fragmentHolder, fragment)
transaction.addToBackStack(null)
transaction.commit()
}
fun createRecipeEight(){
val transaction = manager.beginTransaction()
val fragment = RecipeEight()
transaction.replace(R.id.fragmentHolder, fragment)
transaction.addToBackStack(null)
transaction.commit()
}
}
you can do something like this in when to change background of that button
when (v?.id){
R.id.recipeOneButton -> {
createRecipeOne()
changebackground(id)
}
R.id.recipeTwoButton -> {
createRecipeTwo()
changebackground(id)
}
}
in changebackground(id) pass the id of that button for you want to change background .
For changing other buttons background . if you want to reduce the code . you have to do something like this .
private void changeColor(Button[] buttons){
for(int x=0; x < buttons.length; x++){
buttons[x].setBackgroundColor(Color.RED);
}
}
but for that , you have to initialise button like this
btns[0] = (Button) findViewById(R.id.recipeOneButton );
btns[1] = (Button) findViewById(R.id.recipeTwoButton );
Basically, you need to map button ids to Fragments.
To avoid the redundancy in your functions createRecipeX, you should let your when expression return the proper fragment, rather than calling a function:
override fun onClick(v: View) {
val fragment = when (v.id) {
R.id.recipeOneButton -> RecipeOne()
R.id.recipeTwoButton -> RecipeTwo()
// ...
else -> throw IllegalStateException()
}
val transaction = manager.beginTransaction()
transaction.replace(R.id.fragmentHolder, fragment)
transaction.addToBackStack(null)
transaction.commit()
// change button background
}
Then you can move all the code that they have in common below the when expression.
If the button background is unique for each button you would do it like this:
val fragment = when (v.id) {
R.id.recipeOneButton -> {
// change button background to specific color 1
RecipeOne()
}
R.id.recipeTwoButton -> {
// change button background to specific color 2
RecipeTwo()
}
// ...
else -> throw IllegalStateException()
}

Categories

Resources