Fragment in Android
Overview
Fragment is also called sub-activity. It is an application component which is dependent on Fragment. Following is the Fragment lifecycle.
Steps to follow
- Enable view binding in build.gradle (Module) file to use ActivityMainBinding.
buildFeatures {
viewBinding true
}
- Create blank fragments inside an Android project. It will create a Java/Kotlin file and the associated xml file.
- Extend the Java/Kotlin file with Fragment class. (When you create a new Fragment on Android Studio, the studio automatically creates basic boilerplate codes for you).
class FragmentA : Fragment()
{
private lateinit var communicator : Communicator
override fun onCreateView(
inflater : LayoutInflater , container : ViewGroup? , savedInstanceState : Bundle?
) : View?
{
communicator = activity as Communicator
val view = inflater.inflate(R.layout.fragment_a , container, false)
view.btnSend.setOnClickListener {
communicator.passData(view.edtMessage.text.toString())
}
return view
}
}
class FragmentB : Fragment()
{
override fun onCreateView(
inflater : LayoutInflater , container : ViewGroup? , savedInstanceState : Bundle?
) : View?
{
val view = inflater.inflate(R.layout.fragment_b , container , false)
view.tvMessage.text = arguments?.getString("message")
return view
}
}
- Define the xml file.
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".fragments.FragmentA">
<TextView
android:id="@+id/tvActivityLabel"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerHorizontal="true"
android:layout_marginTop="20dp"
android:text=""
android:textSize="18sp" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@+id/tvActivityLabel"
android:layout_centerHorizontal="true"
android:layout_marginTop="20dp"
android:text="Fragment A"
android:textSize="18sp" />
<EditText
android:id="@+id/edtMessage"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerHorizontal="true"
android:layout_centerVertical="true"
android:ems="10"
android:text="Hello World"
android:textAlignment="center" />
<Button
android:id="@+id/btnSend"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@+id/edtMessage"
android:layout_centerHorizontal="true"
android:layout_marginTop="10dp"
android:text="Send" />
<Button
android:id="@+id/btnStart"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@+id/btnSend"
android:layout_centerHorizontal="true"
android:layout_marginTop="10dp"
android:text="Start" />
</RelativeLayout>
- Create an interface to communicate between fragments. This interface is only implemented in the Activity which is used to launch the fragments.
interface Communicator
{
fun passData(editTextInput : String)
}
- Use the instance of ActivityMainBinding, Fragment and FragmentManager (supportFragmentManager) in the MainActivity (or the one where you are using adding the fragments)
Below is the code of supportFragmentManager.beginTransaction().replace(... , ...)
class MainActivity : AppCompatActivity() , Communicator
{
private lateinit var binding : ActivityMainBinding
override fun onCreate(savedInstanceState : Bundle?)
{
super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
/*This check is used to avoid calling Fragment lifecycles twice
* Check Reference 1 for more details
* */
if (savedInstanceState == null)
{
val fragmentA = FragmentA()
supportFragmentManager.beginTransaction().replace(R.id.fragmentMainContainer , fragmentA)
.commit()
}
}
override fun passData(editTextInput : String)
{
val bundle = Bundle().apply {
putString("message" , editTextInput)
}
val fragmentB = FragmentB()
fragmentB.arguments = bundle
supportFragmentManager.beginTransaction().replace(R.id.fragmentMainContainer , fragmentB)
.commit()
}
}
Below is the code for supportFragmentManager.beginTransaction().add(... , ...)
class MainActivity : AppCompatActivity() , Communicator
{
private lateinit var binding : ActivityMainBinding
private var fragmentA : FragmentA? = null
private var fragmentB : FragmentB? = null
override fun onCreate(savedInstanceState : Bundle?)
{
super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)/*This check is used to avoid calling Fragment lifecycles twice
* Check Reference 1 for more details
* */
if (savedInstanceState == null)
{
fragmentA = FragmentA()
supportFragmentManager.beginTransaction().add(R.id.fragmentMainContainer , fragmentA !!)
.commit()
}
}
override fun passData(editTextInput : String)
{
val bundle = Bundle().apply {
putString("message" , editTextInput)
}
fragmentB = FragmentB()
fragmentB?.arguments = bundle
val transaction = this.supportFragmentManager.beginTransaction()
transaction.hide(fragmentA !!)
transaction.add(R.id.fragmentMainContainer , fragmentB !!).commit()
}
}
- Hello World!!!
Passing Objects between Fragments
- To pass objects between fragments you need to use a serialized data class. Below is the code.
data class Person(val firstName : String , val lastName : String) : Serializable
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<androidx.fragment.app.FragmentContainerView
android:id="@+id/fragmentContainer"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
class MainActivity : AppCompatActivity()
{
override fun onCreate(savedInstanceState : Bundle?)
{
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val firstFragment = FirstFragment()
supportFragmentManager.beginTransaction()
.replace(R.id.fragmentContainer , firstFragment , "First Fragment").commit()
}
}
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".FirstFragment">
<EditText
android:id="@+id/edtFirstName"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="200dp"
android:ems="10"
android:hint="First Name"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<EditText
android:id="@+id/edtLastName"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="20dp"
android:ems="10"
android:hint="Last Name"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/edtFirstName" />
<Button
android:id="@+id/btnNextScreen"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="20dp"
android:text="Next"
android:textAllCaps="false"
app:layout_constraintEnd_toEndOf="@+id/edtLastName"
app:layout_constraintStart_toStartOf="@id/edtLastName"
app:layout_constraintTop_toBottomOf="@+id/edtLastName" />
</androidx.constraintlayout.widget.ConstraintLayout>
class FirstFragment : Fragment() , View.OnClickListener
{
private lateinit var mContext : MainActivity
override fun onAttach(context : Context)
{
super.onAttach(context)
mContext = context as MainActivity
}
override fun onCreateView(
inflater : LayoutInflater , container : ViewGroup? , savedInstanceState : Bundle?
) : View?
{
return inflater.inflate(R.layout.fragment_first , container , false)
}
override fun onStart()
{
super.onStart()
btnNextScreen.setOnClickListener(this)
}
override fun onClick(view : View?)
{
if (view?.id == R.id.btnNextScreen)
{
if (edtFirstName.text.toString().isNotEmpty() && edtLastName.text.toString()
.isNotEmpty()
)
{
val person = Person(edtFirstName.text.toString() , edtLastName.text.toString())
val bundle = Bundle().apply {
putSerializable("Person_Object" , person)
}
val secondFragment = SecondFragment()
secondFragment.arguments = bundle
val transaction = mContext.supportFragmentManager.beginTransaction()
transaction.replace(R.id.fragmentContainer , secondFragment , "Second Fragment")
.commit()
transaction.addToBackStack("First Fragment")
} else if (edtFirstName.text.toString().isEmpty())
{
edtFirstName.error = "First Name can't be blank!!!"
} else if (edtLastName.text.toString().isEmpty())
{
edtLastName.error = "Last Name can't be blank!!!"
}
}
}
}
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".SecondFragment">
<TextView
android:id="@+id/tvFirstName"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="200dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/tvLastName"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="20dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/tvFirstName" />
</androidx.constraintlayout.widget.ConstraintLayout>
class SecondFragment : Fragment()
{
private lateinit var mContext : MainActivity
private lateinit var person : Person
override fun onAttach(context : Context)
{
super.onAttach(context)
mContext = context as MainActivity
}
override fun onCreateView(
inflater : LayoutInflater , container : ViewGroup? , savedInstanceState : Bundle?
) : View?
{
person = arguments?.getSerializable("Person_Object") as Person
return inflater.inflate(R.layout.fragment_second , container , false)
}
override fun onStart()
{
super.onStart()
tvFirstName.text = person.firstName
tvLastName.text = person.lastName
}
}
Do's and Dont's
- Do not declare a Fragment in AndroidManifest.xml file.
- Do not implement the interface (One which you have created to communicate between the Fragments) in the Fragment class.
- Do not try to access components / user interfaces of a Fragment in an Activity file. Accessing these components / user interfaces in your onCreate() lifecycle of the Activity. It will crash your application
- Do not try to initialize components / user interfaces of the Fragment in onAttach(), onCreate(), onCreateView(), lifecycle of a Fragment. It will crash your application. If you want to initialize components / user interfaces of a Fragment, initialize it in the methods after onActivityCreated() and onResume() methods
- Do inflate the layout of fragment
val view = inflater.inflate(R.layout.fragment_a , container , false)
- Do not forget to add the last parameter. Otherwise the application will crash.
- In order to use addToBackStack() method you need to give tag to the fragments.
supportFragmentManager.beginTransaction()
.replace(R.id.fragmentContainer , firstFragment , "First Fragment").commit()
- Hello World!!!
Reference
- https://aalishan565.medium.com/correlation-between-activity-lifecycle-and-fragment-lifecycle-ff0a2c3d9e2f
- https://medium.com/androiddevelopers/the-android-lifecycle-cheat-sheet-part-iii-fragments
- https://github.com/iamraajkanchan/Fragment_Android
- https://github.com/iamraajkanchan/Serialization_Android
- https://medium.com/nerd-for-tech/fragmenttransaction-hide-findings-5b998e959c82
Comments
Post a Comment