Sometimes users who just start using fragments have many questions about a fragment's lifecycle. And how to transfer data between fragments themselves or between fragments and activity? This is precisely what communicating to activity and result listener are for.
Fragment lifecycle methods
The lifecycle of a fragment is slightly different from that of an activity since the fragment itself is completely dependent on the activity and doesn't exist on its own. So, many methods are either named the same as activity methods or have very similar names:
-
onAttach()— the method called when the fragment is attached to the activity. -
onCreate()— the method called when a fragment is created. -
onCreateView()— the method which creates and returns the view hierarchy associated with the fragment. You can use it to inflate your layout. -
onViewCreated()— the method called when the views have been created and attached. All views must be initialized here.
class YourFragmentName : Fragment() {
private var someFirstView: SomeView? = null
private var someSecondView: SomeView? = null
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
someFirstView = view.findViewById(R.id.firstViewId)
someSecondView = view.findViewById(R.id.secondViewId)
}
}
-
onActivityCreated()— the method called after theActivity.onCreate()method. -
onStart()— the method called when the fragment becomes visible to the user (see theActivity.onStart()method). -
onResume()— the method called before the fragment becomes available for user interaction (see theActivity.onResume()method). -
onPause()— the user doesn't interact with the fragment but part of the fragment is visible to the user. -
onStop()— the method that makes the fragment invisible to the user. -
onSaveInstanceState()— the method that saves the fragment state. -
onDestroyView()– the fragment clears the resources associated with the view hierarchy. Here you have to null all your views because holding destroyed views can cause a memory leak.
override fun onDestroyView() {
super.onDestroyView()
someFirstView = null
someSecondView = null
}
-
onDestroy()– the method called before the fragment is destroyed. -
onDetach()– method called before the fragment is detached from the activity.
Fragment and activity lifecycle are very similar but have minor differences. First and foremost, a fragment can't exist without an activity. This means that the activity will always be created first.
Also, if you remove a fragment using the FragmentTransaction remove() or replace() method and add it to the back stack, the onDestroyView() method will still be called but the onDestroy() method will not. As for onCreateView(), onViewCreated() and onStart(), they are called immediately when returning. If not added to the back stack, the fragment will be destroyed using the onDestroy() method.
Communicating to activity
Sometimes the data from the fragment needs to be transferred to the activity. For this, it is best to create an interface in a fragment and subsequently implement it in the desired activity.
In order to pass something to an activity, first, you need to create an interface in the desired fragment.
class YourFragment : Fragment() {
override fun onCreateView(...) { ... }
interface YourFragmentListener {
fun doSomething()
}
}
Next, you need to implement your interface in the desired activity.
class MainAсtivity : AppCompatActivity(), YourFragment.YourFragmentListener {
...
override fun doSomething() {
Log.d("Log", "Doing something!")
}
}
When you are implementing your interface, be sure to include the fragment name before the interface name: YourFragment.YourFragmentListener.
The penultimate step is to get an instance of the Activity implementing your interface in the Fragment.
private var callback: YourFragmentListener? = null
override fun onAttach(context: Context) {
super.onAttach(context)
callback = context as YourFragmentListener
}
override fun onDetach() {
super.onDetach()
callback = null
}
Finally, you only need to call the interface instance at the location you want, such as the onClick() button.
Result listener
Android has a very useful API for passing data between fragments — the Fragment Result API. It's a tool that makes it possible to pass data between fragments using a user-specified key. All this happens with a FragmentManager — parentFragmentManager and childFragmentManager. So, for starters, it’s worth figuring out which manager to use in your case since it's the only thing that makes a difference.
If your fragments are independent of each other, it's a good idea to use parentFragmentManager.
In cases when one fragment is a child fragment, the parent fragment must use childFragmentManager, and the child fragment must use parentFragmentManager.
After selecting the desired FragmentManager in the fragment which will send data, call the setFragmentResult() method. In this method, set the result in the FragmentManager identical to the first one and use the same key. The parameters of this method are the key itself and the Bundle object that needs to be passed to another fragment.
button.setOnClickListener {
parentFragmentManager.setFragmentResult("key", bundleOf("bundleKey" to yourObject))
}
Next, in the onCreate() method of the fragment where you want to receive your data, you need to call the setFragmentResultListener() method. The first parameter of this method is the key parameter: it must be the same as the key you specified in the other fragment.
YourFragmentManager.setFragmentResultListener("key", this) { key, bundle ->
val result = bundle.get("bundleKey")
}
The received data can, for example, be output to the log or displayed to the user. This can be a user profile similar to the one shown in the example below:
Here is the first fragment with an empty profile, and then a completed profile after entering information in the next fragment:
class DefaultFragment : Fragment() {
private var enterData: Button? = null
private var showName: TextView? = null
private var showAge: TextView? = null
private var showUniversity: TextView? = null
private var name: String? = null
private var age: String? = null
private var university: String? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
parentFragmentManager.setFragmentResultListener("requestKey", this) { key, bundle ->
name = bundle.getString("Name")
age = bundle.getString("Age")
university = bundle.getString("University")
showName?.text = name
showAge?.text = age
showUniversity?.text = university
}
}
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
return inflater.inflate(R.layout.fragment_default, container, false)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
enterData = view.findViewById(R.id.enterData)
showName = view.findViewById(R.id.showName)
showAge = view.findViewById(R.id.showAge)
showUniversity = view.findViewById(R.id.showUniversity)
enterData!!.setOnClickListener {
parentFragmentManager
.beginTransaction()
.replace(R.id.container, InputFragment())
.addToBackStack(null)
.commit()
}
}
override fun onDestroyView() {
super.onDestroyView()
enterDate = null
showName = null
showAge = null
showUniversity = null
}
}
Here is the second fragment where the user enters data:
class InputFragment : Fragment() {
private var sendData: Button? = null
private var enterName: EditText? = null
private var enterAge: EditText? = null
private var enterUniversity: EditText? = null
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
return inflater.inflate(R.layout.fragment_first, container, false)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
sendData = view.findViewById(R.id.sendData)
enterName = view.findViewById(R.id.nameEditText)
enterAge = view.findViewById(R.id.ageEditText)
enterUniversity = view.findViewById(R.id.universityEditText)
sendData!!.setOnClickListener {
parentFragmentManager.setFragmentResult(
"requestKey",
bundleOf(
"Name" to enterName?.text.toString(),
"Age" to enterAge?.text.toString(),
"University" to enterUniversity?.text.toString(),
)
)
parentFragmentManager.popBackStack()
}
}
override fun onDestroyView() {
super.onDestroyView()
sendData = null
enterName = null
enterAge = null
enterUniversity = null
}
}
The fragment accepts all values passed to it and displays them on the screen:
Conclusion
Fragment and Activity are very similar in their purpose, but they have quite a few differences in their lifecycle and their use. When you use the Fragment Result API, be sure that you choose the right fragment manager for it to work as intended, and always check that the key is correct, otherwise data can't be retrieved