It is an architectural infrastructure that establishes a relationship between activity and fragment in android, and also provides transitions and management between fragments.
- Existing fragment initialization according to fragment type
- Switching between fragments by command type
- Base class that runs and organizes the whole process: MyFragmentManager.kt
- Custom backstack management
- Interface for fragment interaction
First of all, view binding, which is recommended by Google, was used in the project.
In the build.gradle
file, it is stated that we will use view binding.
buildFeatures {
viewBinding true
}
Binding is done in Activity and Fragment files
private lateinit var binding: ActivityMainBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater)
val view = binding.root
setContentView(view)
}
The application starts with MainActivity. It is used to hold frame layout fragments in activity_main.xml.
All fragments must implement the MyFragment class.
Types of all fragments are specified with enum class.
OnFragmentInteractionListener interface is used to switch between fragments.
When the application is first opened, it starts with MainActivity. The first fragment is displayed by calling the initFragment method under OnCreate. The initFragment method sets the current fragment using the myFragmentManager base class.
private lateinit var binding: ActivityMainBinding
private val myFragmentManager = MyFragmentManager(this, supportFragmentManager)
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater)
val view = binding.root
setContentView(view)
initFragment()
}
private fun initFragment(){
myFragmentManager.setCurrentFragmentType(MyFragmentType.HOME_FRAGMENT, null, false)
}
The fragment manager determines the current fragment with the setCurrentFragmentType method.
fun setCurrentFragmentType(fragmentType: MyFragmentType, arguments: Bundle?, addToBackStack: Boolean) {
if (currentFragment?.fragmentType === fragmentType) return
if (!addToBackStack) clearBackStack()
currentFragment = when (fragmentType) {
MyFragmentType.MY_FRAGMENT_TYPE_A -> FragmentA()
MyFragmentType.MY_FRAGMENT_TYPE_B -> FragmentB()
MyFragmentType.HOME_FRAGMENT -> HomeFragment()
}
currentFragment?.arguments = arguments
if (addToBackStack){
myFragmentStack.add(currentFragment)
}else{
baseFragment = currentFragment
}
val fragmentTransaction = fragmentManager.beginTransaction()
if (addToBackStack){
fragmentTransaction.replace(R.id.activity_main_fragment_container, currentFragment as Fragment)
.addToBackStack(fragmentType.toString())
}else{
fragmentTransaction.replace(R.id.activity_main_fragment_container, currentFragment as Fragment)
}
try {
fragmentTransaction.commit()
}catch (e: Exception){
e.printStackTrace()
}
}
The setCurrentFragmentType method takes an argument of type MyFragmentType in order to traverse the fragment. In addition, if we want to send an argument (Bundle) while switching between fragments, we accept it as a parameter and if we want to add the relevant fragment to bacstack, we need to send the addToBackStack parameter as true.
currentFragment and baseFragment are objects of type MyFragment. myFragmentStack is a Stack structure containing MyFragment.
private var currentFragment: MyFragment? = null
private var baseFragment: MyFragment? = null
private val myFragmentStack = Stack<MyFragment>()
myFragment is the class that determines the structure of all fragments.
abstract class MyFragment : Fragment() {
abstract val fragmentType: MyFragmentType
}
Every fragment must have a type. The setCurrentFragmentType method determines the current fragment according to this type. The type here is specified with the enum class. There should be as many types as the number of fragments.
enum class MyFragmentType {
HOME_FRAGMENT,
MY_FRAGMENT_TYPE_A,
MY_FRAGMENT_TYPE_B
}
The scenario for displaying homeFragment is as above. Since the addToBackStack parameter is false in the initFragment, the homeFragment is not added to the backstack and the application exits when the back button is pressed.
Thanks to the buttons in homeFragment, transitions can be made between fragments. The OnFragmentInteraction interface runs the setCurrentFragmentType method in the fragment manager according to the command type.
private var mListener: OnFragmentInteractionListener? = null
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
val isUserPushed = arguments?.get("isUserPushedScreen")
Log.e("TEST", "Fragment Home isUserPushed: ${isUserPushed}")
binding.homeFragmentGotoFragmentA.setOnClickListener {
mListener?.onFragmentInteraction(MyFragmentCommand.GO_TO_FRAGMENT_A, null, true)
}
binding.homeFragmentGotoFragmentB.setOnClickListener {
mListener?.onFragmentInteraction(MyFragmentCommand.GO_TO_FRAGMENT_B, null, true)
}
}
When the listener is triggered, the onFragmentInteraction method in MainActivity is called and fragment passage is provided.
override fun onFragmentInteraction(command: MyFragmentCommand, argumentData: Bundle?, addToBackStack: Boolean) {
var argBundle: Bundle? = argumentData
if (argBundle == null){
argBundle = Bundle()
}
//default arguments
argBundle.putBoolean("isUserPushedScreen", true)
when(command){
MyFragmentCommand.GO_TO_FRAGMENT_A -> {
myFragmentManager.setCurrentFragmentType(MyFragmentType.MY_FRAGMENT_TYPE_A, argBundle, addToBackStack)
}
MyFragmentCommand.GO_TO_FRAGMENT_B -> {
myFragmentManager.setCurrentFragmentType(MyFragmentType.MY_FRAGMENT_TYPE_B, argBundle, addToBackStack)
}
}
}