top of page

Kotlin: Using Fragments and the Back Stack

This Kotlin project sample illustrates how to launch fragments from an activity, how to pass data from one fragment to another and how to navigate the back stack.

This tutorial assumes that you know how to create classes, activities and fragments in Android Studio.

You can find the full project code on GitHub:


This project contains one activity (MainActivity) and two fragments (Fragment1 and Fragment2). In order to keep things simple, I didn’t bother much with the layout constraints. My eye-balling arrangements is not that bad so I used the “Infer Constraints” option to automatically provide the layout constraints.

The MainActivty (ID: host) contains three buttons: button1 to call fragment1, button2 to call fragment2 and buttonStats to display the fragments that we have in the Back Stack. The fragments are arranged in a Stack called Back Stack that has a LIFO structure (Last Input First Output) and they go on the Stack in the order of their opening.


I’ve purposely kept the code as barebones as possible so it's easier to follow, implement, and test as you change it or add in new elements.

When the user clicks button1 or button2 similar things are happening - a fragment is being added into our main activity.

A fragment is an Android component that contains part of the behaviour/UI of an activity. We can have better code modularity and reusability for using fragments.

Each activity has a FragmentManager that manages its fragments. The FragmentManager will take care of adding our two fragments to the container. In order to add, remove or replace a fragment in our activity we can leverage the APIs from the FragmentTransaction but first we need an instance of the FragmentTransaction.

val fragmentTransaction: FragmentTransaction = fragmentManager.beginTransaction()

In the code below, we first check to see if our fragment tagFRAGMENT_1” already exists as we don’t want to create the same fragment each time the user presses the button.

When you add the fragment to your transaction you should use a tag - we use “FRAGMENT_1” and “FRAGMENT_2tags.

fragmentTransaction.add(R.id.host, fragment, "FRAGMENT_1”)

So later on we can iterate through our fragments and check if the fragment already exists.

 val fragments = fragmentManager?.fragments
 if (fragments != null) {
   for (fragment in fragments) {
     if (fragment.tag == "FRAGMENT_1"){
     //we don't want to create a new Fragment each time the user           
     //presses the button
       return
     }
   }
 }

If our fragment doesn’t exist, we create a new instance of our fragment and add the fragment to the activity and to the back stack.

By adding the fragment to the back stack, the transaction is saved to the back stack so the user can reverse the transaction and bring back the previous fragment by pressing the Back button


//Add this fragment to the activity state
fragmentTransaction.add(R.id.host, fragment, "FRAGMENT_1")

//Add this transaction to the back stack. This means that the //transaction will be remembered after it is committed

//This way we can reverse its operation when later popped off
//the stack

fragmentTransaction.addToBackStack(null)

The order in which you add your changes to a FragmentTransaction doesn't matter, except that you must call commit() last.

fragmentTransaction.commit()

Here is the code for the onClickShowFragment1:

fun onClickShowFragment1(view: View){

 val fragmentManager: FragmentManager = supportFragmentManager

 val fragments = fragmentManager?.fragments
 if (fragments != null) {
   for (fragment in fragments) {
     if (fragment.tag == "FRAGMENT_1"){
      //we don't want to create a new Fragment each time the user 
      //presses the button
       return
     }
   }
 }

 val fragment: Fragment = Fragment1()
 val fragmentTransaction: FragmentTransaction = fragmentManager.beginTransaction()

 //Let's add this fragment to the activity state
 fragmentTransaction.add(R.id.host, fragment, "FRAGMENT_1")

 //Add this transaction to the back stack. This means that the 
 //transaction will be remembered after it is committed
 // This way we can reverse its operation when later popped off 
 //the stack

 fragmentTransaction.addToBackStack(null)

 fragmentTransaction.commit()

}

The code for the onClickShowStats is pretty straightforward - we loop through all the fragments on the back stack and display the tag for each fragment, the position on the back stack as well as the total number of fragments currently on the back stack.

fun onClickShowStats(view: View){

 //refers the current class context
 val context = this@MainActivity 

 val fragmentManager: FragmentManager = context.supportFragmentManager
 val fragments = fragmentManager.fragments
 val myFragmentsonBackStack = fragmentManager.backStackEntryCount

 var showMyFragments: String = ""
 for (entry in 0 until fragmentManager.getBackStackEntryCount())   
 {
   showMyFragments = showMyFragments +  fragmentManager.getBackStackEntryAt(entry).getId() + " - " + fragments[entry].tag + "\n"
  }
 showMyFragments = showMyFragments + "no Fragments on Back Stack = " + myFragmentsonBackStack.toString()
 Toast.makeText(context, showMyFragments, Toast.LENGTH_LONG).show()
 
}

Fragment1 contains an EditText (myEditTextFragment1) and a Button (button3). When the user clicks on the button two things are happening. First, we check to see if FRAGMENT_2 already exists and if it doesn’t, we create a new instance of it in order to load it into the activity. Second, we take the text that we entered in the EditText and pass it to the second fragment (fragment2). Since we are not passing large amounts of data we can use the setArguments() method or the arguments property. This method gets a bundle to store your data in. If we had large amounts of data to pass to our fragments we should have used a ViewModel.

A bundle maps string keys to values and it is used to pass data between activities, fragments and other application components.

I should have mentioned this earlier, but this is just an example to show how to communicate / pass data from one fragment to another. Best practice dictates that fragments don’t communicate directly with one another. All inter-fragment communication should take place via the encapsulating activity.

Back to our fragment1. Here is how we create a bundle and associate the string “show_text” with the value we enter in the EditText in fragment1.

val args = Bundle()
args.putString("show_text", myedittext.text.toString())

Then we pass this data to our second fragment.

fragment.arguments = args

If the second fragment already exists, we can refresh the fragment by detaching it and reattaching it.

val fragmentTransaction: FragmentTransaction = fragmentManager.beginTransaction()

fragmentTransaction.detach(fragment)
fragmentTransaction.attach(fragment)
fragmentTransaction.commit()

if the second fragment doesn’t exist we create it and we pass the data to it from the first fragment.

val fragment = Fragment2()
fragment.arguments = args

Below is the method that calls the second fragment from fragment1 and passes the text that we entered into our EditText.

To intercept the button onclick event we have to first find the button in the fragment. The inflater class below is provided by the Android System to provide support for general purpose decompression by using the ZLIB compression library. When you have an XML layout, the layout will be “inflated” by the Android OS system meaning that it will be rendered by creating a view object. (the OS will “inflate” the view for you).

R.layout.* references any layout resource you have created. It can be usually found in /res/layout. R is an auto-generated class, and it describe the resources of your project. R contains the definitions for all resources of a particular application package. layout is one of them.

val root = inflater.inflate(R.layout.fragment_1, container, false)
val btn = root.findViewById<View>(R.id.button3) as Button

Them we implement setOnClickListener method of the button that we found in fragment1.

btn.setOnClickListener {
    …
}

class Fragment1 : Fragment() {

 override fun onCreateView(
 inflater: LayoutInflater, container: ViewGroup?,
 savedInstanceState: Bundle?
 ): View? {

 var fragmentExists: Boolean
 val root = inflater.inflate(R.layout.fragment_1, container, false)
 val btn = root.findViewById<View>(R.id.button3) as Button
 val myedittext = root.findViewById<View>(R.id.myEditTextFragment1) as EditText

 btn.setOnClickListener {

 fragmentExists = false

 val fragments = fragmentManager?.fragments
 if (fragments != null) {
   for (fragment in fragments) {
     if (fragment.tag == "FRAGMENT_2"){
       fragmentExists = true
     }
   }
 }

 val args = Bundle()
 args.putString("show_text", myedittext.text.toString())
 val context = activity as AppCompatActivity

 if (fragmentExists) {
 
     val fragmentManager: FragmentManager =     
     context.supportFragmentManager

     val fragment = 
     fragmentManager?.findFragmentByTag("FRAGMENT_2")
     if (fragment != null) {
         fragment.arguments = args
         val fragmentTransaction: FragmentTransaction =     
         fragmentManager.beginTransaction()
         fragmentTransaction.detach(fragment)
         fragmentTransaction.attach(fragment)
         fragmentTransaction.commit()
     }

 }else{

     val fragment = Fragment2()
     fragment.arguments = args

     //Let's add this fragment to the activity state
     val fragmentTransaction = 
     fragmentManager?.beginTransaction()
     
     if (fragmentTransaction != null){

         fragmentTransaction.add(R.id.host, fragment, 
         "FRAGMENT_2")

         //Add this transaction to the back stack. This means 
         //that the transaction will be remembered after it is 
         //committed
         //This way we can reverse its operation when later 
         //popped off the stack

         fragmentTransaction.addToBackStack(null)
         fragmentTransaction.commit()
         fragmentExists = true
     }
   }

 }

 return root
 }

}

Here is as screenshot of some of the scenarios that we described above. Hope this will help.

Happy coding!




8,530 views0 comments

コメント


bottom of page