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.

Activity with Fragment rotated

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


 

Comments

Popular posts from this blog

Architecture Components in Android

DataBinding in Android

SSLSocketFactory in Android