Fragment can be included as a part of Activity as well as View.
And the difference is that View does not have the same lifecycle as Activity, but Fragment has lifecycle.
Fragment is included as a part of Activity like View, but it is used when life cycle processing is required like Activity.
For reference, the following is a list of classes that inherit Fragment.
DialogFragment, ListFragment, PreferenceFragment, WebViewFragment
Create layout xml file to be used in Fragment.
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout
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">
<TextView
android:id="@+id/textMessage"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:text="Hello"
android:gravity="center"
android:textSize="20sp"
tools:ignore="HardcodedText" />
</FrameLayout>
Add the Fragment class containing the xml file.
class XmlFragment: Fragment() {
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
return inflater.inflate(R.layout.fragment_hello, container, false)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
}
}
Add FragmentContainerView to the xml file and add the package name and class name of the fragment to the android:name attribute.
<androidx.fragment.app.FragmentContainerView
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/fragment_container_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:name="com.devez.app.android.fragment.xml.XmlFragment" />
If you create an Activity and inflate the xml file containing the Fragment, the Fragment is added to the Activity.
class XmlFragmentActivity: AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_fragment_xml)
}
}
The following is how to create a Fragment with code.
Create layout xml file to be used in Fragment.
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout
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">
<TextView
android:id="@+id/textMessage"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
android:textSize="20sp"
tools:ignore="HardcodedText" />
</FrameLayout>
It is the source code of the Fragment to be added as code to the Activity.
class CodeFragment: Fragment() {
private var message: String? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
message = arguments?.getString(MESSAGE)
}
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
return inflater.inflate(R.layout.fragment_code, container, false)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
view.findViewById<TextView>(R.id.textMessage).text = message
}
companion object{
const val MESSAGE = "message"
fun newInstance(message: String): Fragment {
val fragment = CodeFragment()
fragment.arguments = Bundle().apply {
putString(MESSAGE, message)
}
return fragment
}
}
}
Add the FragmentContainerView layout to which the Fragment will be added to the xml file.
Fragment is added to FragmentContainerView.
<androidx.fragment.app.FragmentContainerView
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/fragment_container_view"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
Create an Activity and inflate the xml file containing the Fragment.
It is an empty Activity that has not yet added a Fragment.
class CodeFragmentActivity: AppCompatActivity(){
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_fragment_code)
}
Add Fragment to Activity.
val newFragment = CodeFragment.newInstance(getNextCount())
val transaction = supportFragmentManager.beginTransaction()
transaction.replace(R.id.fragment_container, newFragment, "myTag")
transaction.addToBackStack(null)
transaction.commit()
We have seen how to add a Fragment with xml and code.
Next, let's look at a few more items needed to work with Fragments.
onCreateView is a method that creates a View, and onViewCreated is a method to which code necessary to process the created View is added.
onCreateView is responsible for creating and returning a View.
You can access the View in onCreateView, but since the View has not yet been created and added to the Fragment, in onCreateView, only add the code that creates and returns the View.
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
return inflater.inflate(R.layout.fragment_code, container, false)
}
In onViewCreated, the View created in onCreateView is passed as an argument.
Since the View is completely created, the code to control and process the View and its child Views is added to the onCreateView method.
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
textView = view.findViewById(R.id.textMessage)
if(message != null){
textView.text = message
}
}
In the source code written above, the replace method of the FragmentTransaction class was used.
The replace method removes the added Fragment and adds a new Fragment if another Fragment has already been added to the FragmentContainer.
When the add method is used, a new Fragment is added on top of the already added Fragment.
You can put a tag name as the last argument of the replace method, and you can pass null as the tag name is an optional argument.
The advantage of passing the tag name as an argument is that the added Fragment can be found with the findFragmentByTag method of the FragmentManager class.
How do I pass the information to be passed when creating a Fragment?
For a general class, there is a way to pass the necessary information to the constructor.
However, in the case of a Fragment, the information passed to the constructor may disappear when the Fragment is regenerated.
When the system thinks that memory management is necessary, the fragment is destroyed, and when memory is freed, the fragment is created again. This is because the default constructor is used when re-creating it.
If the constructor of a Fragment is overloaded, the following error occurs when the Fragment is re-created.
Error message: Fragment$InstantiationException: Unable to instantiate fragment make sure class name exists, is public, and has an empty constructor that is public
The error occurs because the system cannot create a Fragment using the default constructor if the Fragment's constructor is overloaded.
If you overload a constructor in Java, you know that you cannot create an object with the default constructor.
So, although it is not recommended, if you need to overload a constructor, you should add an empty default constructor to prevent an error from occurring.
The way to pass the necessary information to the Fragment without overloading the constructor is to use the Bundle class.
Below is the code to create a Fragment, store a message in the Bundle object, and set it as arguments of the Fragment.
val fragment = CodeFragment()
fragment.arguments = Bundle().apply {
putString("message", message)
}
return fragment
The data passed to the Bundle is passed as an argument to the onCreate method of the Fragment.
Even when a Fragment is destroyed and re-created by the system, the data stored in the Bundle does not disappear and is passed as an argument to the onCreate method.
If you use Bundle, the data is maintained even if the Fragment is re-created without overloading the constructor, but the code that creates the Fragment will increase.
Whenever you create a Fragment, create a Fragment and Bundle object, store data in the Bundle object, and set it as arguments of the Fragment.
If fragments are used in many places, there is a disadvantage of repeating the same code, and this disadvantage can be solved by using a factory method.
The factory method name is conventionally newInstace.
The newInstace method returns the created Fragment.
companion object{
const val MESSAGE = "message"
fun newInstance(message: String): Fragment {
val fragment = CodeFragment()
fragment.arguments = Bundle().apply {
putString(MESSAGE, message)
}
return fragment
}
}
You can create a Fragment object to which the necessary information is passed by calling the newInstace method as follows in the place where the Fragment is created.
val newFragment = CodeFragment.newInstance("Hello")
When adding or removing a Fragment to or from a FramentContainer, the beginTransaction method should be called first and the commit method should be called last.
Let's look at the code we wrote above again.
val newFragment = CodeFragment.newInstance(getNextCount())
val transaction = supportFragmentManager.beginTransaction()
transaction.replace(R.id.fragment_container, newFragment, "myTag")
transaction.addToBackStack(null)
transaction.commit()
The code written between the beginTransaction method and the commit method becomes one transaction.
The addToBackStack method is optional, and if the addToBackStack method is called before the commit method is called, the transaction is rolled back when the back key is pressed.
The argument of the addToBackStack method is optional, and a null value may be passed.
The advantage when the argument of the addToBackStack method is used is that the state can be returned to a specific point in time by using the popBackStack method of the FragmentManager.
If you do not want to rollback the transaction when the back key is pressed, do not use addToBackStack as shown in the code below.
val newFragment = CodeFragment.newInstance(getNextCount())
val transaction = supportFragmentManager.beginTransaction()
transaction.replace(R.id.fragment_container, newFragment, "myTag")
transaction.commit()
class ExampleActivity : AppCompatActivity(R.layout.example_activity) {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
if (savedInstanceState == null) {
supportFragmentManager.commit {
setReorderingAllowed(true)
add<ExampleFragment>(R.id.fragment_container_view)
}
}
}
}
The order of adding Fragment to Activity is as follows.
1. Fragment 생성
2. FragmentActivity의 getSupportFragmentManager 메소드를 호출해서 FragmentManager 객체를 저장
3. FragmentManager 클래스의 beginTransaction 함수호출
4. replace, add, remove 같은 Fragment 처리
5. FragmentManager 클래스의 commit 함수 호출
Here, 1, 2, 3, and 5 are repetitive tasks.
Only the part processed by Fragment No. 4 is changed, and most of it is processed by calling the method of the FragmentManager class.
If you include fragment-ktx, you can omit the repeated part as follows and pass only the part that processes the Fragment as a lambda expression.
supportFragmentManager.commit {
setReorderingAllowed(true)
replace<CodeFragment>(R.id.fragment_container)
}
fragment-ktx can be included in Gradle like this:
dependencies {
val fragment_version = "1.3.6"
// Java language implementation
implementation("androidx.fragment:fragment:$fragment_version")
// Kotlin
implementation("androidx.fragment:fragment-ktx:$fragment_version")
}
Let's compare the code with and without fragment-ktx
private fun replaceFragment(){
val newFragment = CodeFragment.newInstance(getNextCount())
val transaction = supportFragmentManager.beginTransaction()
transaction.replace(R.id.fragment_container, newFragment, "myTag")
transaction.addToBackStack(null)
transaction.commit()
}
private fun replaceFragment(){
supportFragmentManager.commit {
addToBackStack(null)
replace<CodeFragment>(R.id.fragment_container, "myTag", args = null)
}
}
setReorderingAllowed optimizes setReorderingAllowed of a Fragment.
If multiple Transactions are executed at the same time, there may be meaningless actions. Setting the setReorderingAllowed method to true prevents unnecessary actions from being processed.
The following is the Fragment add sample code that calls the setReorderingAllowed method.
supportFragmentManager.commit {
setReorderingAllowed(true)
add<CodeFragment>(R.id.fragment_container, args = bundle)
}