Receive fragment result in Navigation Component Fragment - android

I have implemented NavHost(Navigation Component) in MainFragment(that contain NavHost) and it has three other fragment in it's nav (CategoryFragment,GalleryFragment and PreviewFragment)
Above three fragment are sibliing in nav_graph. I want to send a model to our parentFragment(MainFragment)
I have tried two different ways to send data to parentFragment(MainFragment)
PreviewFragment.kt on Button Click
parentFragmentManager.setFragmentResult("requestKey", bundleOf("bundleKey" to args.photo.imageUrl))
findNavController().previousBackStackEntry?.savedStateHandle?.set("requestKey", args.photo.imageUrl)
MainFragment.kt 0nViewCreated
navController.currentBackStackEntry?.savedStateHandle?.getLiveData<String>("requestKey")?.observe(
viewLifecycleOwner) { result ->
Toast.makeText(requireContext(), "$result in MainFragment", Toast.LENGTH_SHORT).show()
}
childFragmentManager.setFragmentResultListener("requestKey") { requestKey, bundle ->
// We use a String here, but any type that can be put in a Bundle is supported
val result = bundle.getString("bundleKey")
Toast.makeText(requireContext(), "$result in MainFragment", Toast.LENGTH_SHORT).show()
// Do something with the result
}
I have seen post that said FragmentManager should be same to handle fragment result api.
I have tried it parentFragmentManager , childFragmentManager and directly.

As there is another Fragment(NavHostFragment) between childFragment and ParentFragment in Navigation Component. I have found this here
In ChildFragment you have to call
parentFragment?.parentFragmentManager?.setFragmentResult
And in ParentFragment(that has NavHost)
childFragmentManager.setFragmentResultListener

You can try these:
requireActivity().supportFragmentManager.setFragmentResult
requireActivity().supportFragmentManager.setFragmentResultListener

Related

How to change action bar navigation onClick listener from fragment

I have a toolbar in activity with a fragment container
<androidx.appcompat.widget.Toolbar
android:id="#+id/tlUsersActivity"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:background="#color/white"
/>
I run these lines to control the backpressed button
setSupportActionBar(binding?.tlUsersActivity)
if (supportActionBar != null) {
when (intent.getStringExtra(MainActivityAdmin.FRAGMENT_TYPE)) {
"View Users" -> {
supportActionBar?.title = viewUsersText
}
"Edit Users" -> {
supportActionBar?.title = editUsersText
}
}
supportActionBar?.setDisplayHomeAsUpEnabled(true)
binding?.tlUsersActivity?.setNavigationOnClickListener { onBackPressed() }
I want to change this action from inside the fragment, I want to replace it with a function that replaces fragments.
I've tried this but it didn't work
val actionBar = view?.findViewById<Toolbar>(R.id.tlUsersActivity)
actionBar?.setNavigationOnClickListener {
replaceFragment(FragmentEditUsers())
}
Directly working with Activity from the fragment is not a good practice, it will become a nest of bugs in the future))
The first option is SharedViewModel which you can share between your activity and fragment. But maybe you don't want it if you have simple functionality and you don't want to involve ViewModel here.
The second option, which usually was used before Fragment Result API and SharedViewModel were callbacks.
The third one which I prefer and it's way cleaner is Fragment Result API.
You can find more information here - FragmentResultAPI & SharedViewModel.
Using it you can communicate between activity-to-fragment, vice-versa, and fragment-to-fragment.
You need a Gradle dependency to use it:
implementation "androidx.fragment:fragment-ktx:1.4.1"
In the activity, you have to set a FragmentResultListener. It takes as a parameter the request key, lifecycle, and FragmentResultListener interface which is a SAM interface (has only 1 function). You can deep-dive here if you want.
const val REQUEST_KEY = "101"
const val BUNDLE_KEY = "B101"
// Activity code
supportFragmentManager.setFragmentResultListener(REQUEST_KEY, this) { requestKey, bundle ->
if (requestKey == REQUEST_KEY) {
// Here you can call the functions you desire
Toast.makeText(this, bundle.getString(BUNDLE_KEY), Toast.LENGTH_SHORT).show()
}
}
Now let's have a look at the fragment side. I have one button and after clicking I trigger the result listener in the activity:
// Fragment code
val btnTriggerActivity = view.findViewById<Button>(R.id.btn_trigger_activity)
btnTriggerActivity.setOnClickListener {
// This code will trigger the function in activity.
// The activity will show the toast
setFragmentResult(REQUEST_KEY, bundleOf(
BUNDLE_KEY to "Hello from the other side :)"
))
}
That's it. Here's also an image from the developer's page which shows the communication of fragment-to-fragment using FragmentResultApi.

How to retain fragment state, when it's reopened by the deep link?

I have one fragment page in android, the first time I open it, I get data from bundle and show. Now, when I go to web page from this page and come back by deep-link, I lose my current data
(in other words, I have to go to the web page for payment, after that, I have to come back to the current fragment by deep link without losing current data)
Recently, I've been faced with a the same problem. I'm still a bit newbie in Android, so it's possibly not the best way to do it, but it does work.
First, in the Android Manifest, we have to add the attribute android:launchMode="singleTop" to the Activity that's hosting the Fragment. This way, when we go back to the app via deep link, the old Activity will receive the Intent instead of creating a new instance.
Then, in the code of the hosting Activity, we use the onNewIntent() method to look for the existing attached Fragment. As Hossein Kurd pointed out in his answer, this step depends on how we are managing our Fragments. In my case, I'm using a NavHostFragment, so first I have to find that and then I can search for the Fragment I want to come back to (in my case, an instance of LoginFragment):
override fun onNewIntent(intent: Intent?) {
super.onNewIntent(intent)
if (intent != null && intent.action == Intent.ACTION_VIEW && intent.data != null) {
val navHostFragment = supportFragmentManager.fragments.find { fragment -> fragment is NavHostFragment }
navHostFragment?.let { navHostFrag ->
val loginFragment = navHostFrag.childFragmentManager.fragments.find { childFragment -> childFragment is LoginFragment }
loginFragment?.let { loginFrag ->
val bundle = bundleOf("uri" to intent.data)
loginFrag.arguments = bundle
val fragmentTransaction = loginFrag.parentFragmentManager.beginTransaction()
fragmentTransaction.show(loginFrag).commit()
}
}
}
}
This method will receive the Intent from the browser, with the corresponding data (in my case, the Uri). We wrap the data in a Bundle and pass that to the Fragment's arguments and then show() the Fragment.
Finally, in the onResume() method of the Fragment, we check if the arguments include the data we are expecting, then we can go on with our logic:
override fun onResume() {
super.onResume()
this.arguments?.get("uri")?.let { data ->
// Your logic with the data you received from the browser
}
}
You can use getInstance static method, Better way is update fragment from activity
and the solution depends on the way you call your fragments: SupportFragmentManager, NavController ...
get current fragment by view Id or ...
Kotlin:
supportFragmentManager.primaryNavigationFragment?.childFragmentManager?.fragments?.forEach { fragment ->
// fragment.onActivityResult(requestCode, resultCode, boundle)
fragment.method(param1, param2)
}

Pass data from BottomSheetFragment to previous Fragment

Is there any better way to send back the data to the previous fragment/parent fragment other than listener?
I have a fragment which consists of list of items. Clicking on the items will open a bottom sheet fragment. While closing the bottom sheet popup I need to pass data back to the fragment itself.
What I have done so far is created a listener and implemented it.
It really depends on what components you're using. If you are using Android Jetpack components then check out this article: LINK
You should be able to pass data back and forth similar to passing data with startActivityForResult()
Also, while you're at it please check out the official documentation too, there's a good example that will help you understand this better: LINK
Although you mention any way other than listener, but according to
documents:
Starting with Fragment 1.3.0-alpha04, each FragmentManager
implements FragmentResultOwner. This means that a FragmentManager can
act as a central store for fragment results. This change allows
components to communicate with each other by setting fragment results
and listening for those results...
Sets the FragmentResultListener for a given requestKey. Once the given
LifecycleOwner is at least in the STARTED state, any results set by
setFragmentResult using the same requestKey will be delivered to the
callback. The callback will remain active until the LifecycleOwner
reaches the DESTROYED state or clearFragmentResultListener is called
with the same requestKey.
To pass data back to fragment A from fragment B, first set a result listener on fragment A, the fragment that receives the result. Call setFragmentResultListener() on fragment A's FragmentManager, as shown in below:
in your BottomSheet Class:
btncloseBottomSheet.setOnClickListener {
val result = Bundle().apply {
// put your data in bundle
putInt("MY_KEY", 6)
}
setFragmentResult("requestCode", result)
dismiss()
}
in your previous fragment/parent fragment, you need to implement FragmentResultListener:
class PreviousFragment : FragmentResultListener {
...
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// set fragment listener
parentFragmentManager.setFragmentResultListener(
"requestCode",
viewLifecycleOwner,
this
)
}
...
// get result from other fragments by FragmentResultListener
override fun onFragmentResult(requestKey: String, result: Bundle) {
when (requestKey) {
"requestCode" -> {
val resultFromBundle = result.getInt("MY_KEY")
// Do somthing
}
}
}
}

how to call a Fragment from a other Fragment?

I setup a facebook login product inside my fragment where i put a Graph request to
get some data from graph api after receiving the data i want to goto a different fragment to handle the result but on clicking on login button i remain in same activity only login button get changed to log out button it means click listener is
working .if i try to go in an activity using the intent it working fine new activity get displayed but i need to go to my fragment how can i do that here is my code ?
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
loginButton?.setOnClickListener({
callbackManager = CallbackManager.Factory.create()
loginButton?.setFragment(this)
// loginButton = view.findViewById(R.id.log)
loginButton?.setReadPermissions("email")
loginButton?.registerCallback(callbackManager, object : FacebookCallback<LoginResult> {
override fun onSuccess(loginResult: LoginResult) {
val request: GraphRequest = GraphRequest.newMeRequest(loginResult.getAccessToken()
, GraphRequest.GraphJSONObjectCallback { `object`, response ->
// Override fun onCompleted( `object`:JSONObject, response:GraphResponse) {
Log.e(TAG, `object`.toString())
Log.e(TAG, response.toString())
try {
userId = `object`.getString("id");
profilePicture = URL("https://graph.facebook.com/" + userId + "/picture?width=500&height=500");
if (`object`.has("first_name"))
firstName = `object`.getString("first_name");
if (`object`.has("last_name"))
lastName = `object`.getString("last_name");
if (`object`.has("email"))
email = `object`.getString("email");
if (`object`.has("birthday"))
birthday = `object`.getString("birthday");
if (`object`.has("gender")) {
gender = `object`.getString("gender")
}
/* var main:Intent = Intent(this#MainActivity, Display::class.java)
main.putExtra("name", firstName)
main.putExtra("surname", lastName)
main.putExtra("imageUrl",profilePicture.toString())
startActivity(main);
finish(); */
mainFrameFragment = MainFrameFragment()
parameters?.putString("name", firstName)
parameters?.putString("surname", lastName)
parameters?.putString("imageUrl",profilePicture.toString())
mainFrameFragment?.arguments = parameters
fragmentManager?.beginTransaction()
?.replace(R.id.displayFragment, mainFrameFragment as MainFrameFragment,"MainFrameFragment")
?.commit()
// moveToNewActivity()
} catch (e: JSONException) {
e.printStackTrace();
} catch (e: MalformedURLException) {
e.printStackTrace();
}// App code
})
// mainFrameFragment = MainFrameFragment()
parameters = Bundle()
parameters?.putString("fields", "id, first_name, last_name, email, birthday, gender")
request.setParameters(parameters)
request.executeAsync()
}
override fun onCancel() {
// App code
}
override fun onError(exception: FacebookException) {
// App code
}
})
})
}
If you want to replace the entire Fragment1 with Fragment2, you need to do it inside MainActivity, by using:
Fragment1 fragment2 = new Fragment2();
FragmentManager fragmentManager = getFragmentManager();
FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
fragmentTransaction.replace(android.R.id.content, fragment2);
fragmentTransaction.commit();
Just put this code inside a method in MainActivity, then call that method from Fragment1.
If you have a A/B layout with master detail on the screen sporting 2 fragments, then it makes sense that you want the two fragments to communicate.
The common practice for this is to create an interface with the items that fragment A needs to call in fragment B and visa versa. Then implement the interface in the fragments. Next in fragment A constructor pass in (fragmentBInterface) and visa versa for fragment B.
If you want the lazy answer you can use Otto Bus and fire events to subscriber. That is a simple annotation to catch it and a simple single line to post it, but this uses reflection and many engineers don't like this style coding.
Lastly, if only one fragment is on the screen at one time, then your fragments probably don't have a good reason to communicate in which case allow the activity to mediate content between the fragments. If the login button is all you are trying to do, simply put some sort of a success callback in the activity and create a mainActivity interface that is given to the fragment in it's constructor.
One last note, if a screen is full screen and strictly launches another screen there really is no reason to use a fragment. Some people get too focused on "everything must be a fragment". The framework provides you great options for each use case. Login is usually a full screen activity, along with splash screen. An example of good usage for fragments would be a wizard like registration or a navigation drawer for tier 1 level content of your application. When you drill into a detail I would move into an activity with up enabled.
Hope that helps.

Passing data back to previous fragment from current fragment

I am Using Navigation Drawer in my app. I have one MainActivity and rest of are Fragments. So the issue is Suppose i have three fragments like A,B,C.
Now in A i have one button and i am sending data from A>B.
For example putSring("datafrom A","datafrom A");
Now in B i receive data From A.
I have one button in B,and i am sending data from B>C.
For example putSring("datafrom B","datafrom B");
Now in C i receive data From B.
Then, I have one Button in C,and sending data from C>B.
For example putSring("datafrom C","datafrom C");
So,seems like in B i am getting data from two different fragments. I tried with all using activity and it work well with startActivityforresult. but how can i manager when all are fragments.
UPDATE
Starting with Androidx Activity 1.2.0-alpha02 and Androidx Fragment 1.3.0-alpha4, the official Android developer guide recommends to use the Activity/Fragment Result APIs over the deprecated Activity.onActivityResult(int, int, Intent) and Fragment.setTargetFragment(Fragment, int) methods:
it is strongly recommended to use the Activity Result APIs introduced in AndroidX Activity 1.2.0-alpha02 and Fragment 1.3.0-alpha02.
Thus, to pass data back to fragment B from C, call setFragmentResultListener() on fragment B's FragmentManager, as shown in the following example:
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// Use the Kotlin extension in the fragment-ktx artifact
setFragmentResultListener("requestKey") { requestKey, bundle ->
// We use a String here, but any type that can be put in a Bundle is supported
val result = bundle.getString("bundleKey")
// Do something with the result
}
}
In fragment C, set the result on the same FragmentManager by using the same requestKey using the setFragmentResult() API. Example:
setFragmentResult("requestKey", bundleOf("bundleKey" to "result"))
More details can be found at this guide.
The below answer is deprecated
You may call setTargetFragment() when you start the Fragment C from B. Example:
FragmentC fragmentC = FragmentC.newInstance();
fragmentC.setTargetFragment(FragmentB.this, REQUEST_CODE);
getFragmentManager().beginTransaction().replace(R.id.container, fragmentC).commit();
and then when you want to pass data back to fragment B from C, you can call the following code:
getTargetFragment().onActivityResult(
getTargetRequestCode(),
Activity.RESULT_OK,
new Intent().putExtra("datafrom C", "datafrom C")
);
and get it from the onActivityResult() method in your fragment B:
#Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
if (requestCode==REQUEST_CODE && resultCode==Activity.RESULT_OK) {
String datafromC = data.getStringExtra("datafrom C");
}
}
When u are sending the data from Fragment A to Fragment B use the same boolean like below:-
FragmentA -> FragmentB
FragmentB ldf = new FragmentB ();
Bundle args = new Bundle();
args.putBoolean("BOOLEAN_VALUE",true);
ldf.setArguments(args);
getFragmentManager().beginTransaction().add(R.id.container, ldf).commit();
And when u are send data from Fragment C to Fragment B use the same BOOLEAN which is used in Fragment A to B like below-
FragmentC -> FragmentB
FragmentB ldf = new FragmentB ();
Bundle args = new Bundle();
args.putBoolean("BOOLEAN_VALUE",false);
ldf.setArguments(args);
getFragmentManager().beginTransaction().add(R.id.container, ldf).commit();
And in the last we have to check that value is recevied in FragmentB is from where like Fragment A OR FragemntC
FragmentB
Boolean getValue= getArguments().getBoolean("BOOLEAN_VALUE");
if(getValue)
{
//VALUE RECEIVED FROM FRAGMENT A
}
else
{
//VALUE RECEIVED FROM FRAGMENT C
}
Things changed a lot since 2017. The answer I post is basically an example from https://developer.android.com and it presents a good solution where your fragments, in any number, do not know anything about each other and still you are able to create a simple and elegant mechanism that can be used without much struggle.
The answer is based on ViewModels and LiveData.
Note: If you are not familiar with Architecture Components I strongly advise you to learn about it as much as you can any time you can as it will increase your production speed and decrease the number of errors in your projects.
Everything below is a citation from the following link: source (Kotlin/Java)
Share data between fragments
It's very common that two or more fragments in an activity need to
communicate with each other. Imagine a common case of master-detail
fragments, where you have a fragment in which the user selects an item
from a list and another fragment that displays the contents of the
selected item. This case is never trivial as both fragments need to
define some interface description, and the owner activity must bind
the two together. In addition, both fragments must handle the scenario
where the other fragment is not yet created or visible.
This common pain point can be addressed by using ViewModel objects.
These fragments can share a ViewModel using their activity scope to
handle this communication, as illustrated by the following sample
code:
class SharedViewModel : ViewModel() {
val selected = MutableLiveData<Item>()
fun select(item: Item) {
selected.value = item
}
}
class MasterFragment : Fragment() {
private lateinit var itemSelector: Selector
// Use the 'by activityViewModels()' Kotlin property delegate
// from the fragment-ktx artifact
private val model: SharedViewModel by activityViewModels()
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
itemSelector.setOnClickListener { item ->
// Update the UI
}
}
}
class DetailFragment : Fragment() {
// Use the 'by activityViewModels()' Kotlin property delegate
// from the fragment-ktx artifact
private val model: SharedViewModel by activityViewModels()
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
model.selected.observe(viewLifecycleOwner, Observer<Item> { item ->
// Update the UI
})
}
}
Notice that both fragments retrieve the activity that contains them.
That way, when the fragments each get the ViewModelProvider, they
receive the same SharedViewModel instance, which is scoped to this
activity.
This approach offers the following benefits:
The activity does not need to do anything, or know anything about this
communication.
Fragments don't need to know about each other besides
the SharedViewModel contract. If one of the fragments disappears, the
other one keeps working as usual.
Each fragment has its own lifecycle,
and is not affected by the lifecycle of the other one. If one fragment
replaces the other one, the UI continues to work without any problems.

Categories

Resources