open fragment from activity and pass arguments using navigation components - android

I can navigate from one fragment which is inside of main_graph from navigation components to one activity, I also added the activity inside the main_graph from navigation components:
see the picture below:
My problem is that I can't navigate from the new activity that was added and send arguments or one bundle to the previous fragment as we can see in the picture of above. This activity is not the MainAcitivity. I found this question and on there the guy did something like this:
val bundle = Bundle()
bundle.putString("id", id)
(activity as MainActivity).navController.navigate(R.id.orderDetailFragment, bundle)
I don't understand why for me not works this code: (activity as MainActivity) I think that it should be something like this in my case:
val navController by lazy { findNavController(R.id.main_graph) }
val bundle = Bundle()
bundle.putString("barcode", "123456789")
navController.navigate(R.id.registerProductFragment, bundle)
However I'm getting this error:
Process: com.example.inventariooffline, PID: 15859
java.lang.IllegalArgumentException: ID does not reference a View inside this Activity
at androidx.core.app.ActivityCompat.requireViewById(ActivityCompat.java:371)
at androidx.navigation.Navigation.findNavController(Navigation.java:58)
at androidx.navigation.ActivityKt.findNavController(Activity.kt:30)
I just one return from the activity that I have created to my previous fragment with safe arguments or a bundle.
I hope did be clear guys, thanks so much.

Related

Android: How do I trigger an event once a fragment is totally loaded?

I am trying to load initial data onto a fragment in my application. In the following lines of code (located at the very end of onCreate, I attempt retrieve the fragment and load data onto it:
// Display the current list of matches to the user
var eventsFragment: EventsFragment =
supportFragmentManager.findFragmentByTag("eventsFragment") as EventsFragment
eventsFragment.displaySchedule(currList)
However, I receive the following error:
Caused by: kotlin.TypeCastException: null cannot be cast to non-null
type com.example.alarmfornbamatches.ui.main.EventsFragment
My guess is that the fragment hasn't loaded at the end of onCreate. So how do I execute the displaySchedule function once the fragment is fully loaded and available to be referenced for UI updates?
You can solve the TypeCastException by using this:
// Display the current list of matches to the user
var eventsFragment: EventsFragment? = supportFragmentManager.findFragmentByTag("eventsFragment") as EventsFragment
eventsFragment?.displaySchedule(currList)
You are looking for a fragment that you haven't created, in a place you haven't put it yet. You must first create the fragment and add it to the supportFragmentManager using transactions. If you want do pass data to the fragment you should do so by add this to your fragment (if your list is not string, then it must be serializable and use putSerializable instead.
companion object{
fun newInstance(list: ArrayList<String>) = EventsFragment().apply{
arguments = Bundle().apply {
putStringArrayList(KEY, list)
}
}
}
and then pull the data out of the arguments bundle in the onCreate method in your fragment.
Now in the onCreate in your activity you will create the fragment and add it like so
eventsFragment = EventsFragment.newInstance(list)
supportFragmentManager.beginTransaction().add(R.id.frameLayoutId, eventsFragment, fragmentTag).commit()
only now, if necessary can you use the code you have to get the fragment again, and you should always do it expecting ti to possibly come back as null:
eventsFragment = supportFragmentManager.findFragmentByTag(fragmentTag) as? EventsFragment ?: EventsFragment.newInstance(list)
The problem with your code is you are trying to find a fragment which is not there that's why its null. Not sure where you made the Fragment transaction.
To fix this There can be two cases:
If you already have the data in your Activity its better you pull it inside Fragment by using getActivity() in #onViewCreated() or by a Shared ViewModel.
If you are loading data from a source(Network/IO) then you can use a Shared ViewModel or Get fragment from back stack and call its method or you can use conventional callback stuff.
I can add some sample code but i am not sure which option is best fit for you since its not clear from question that u are using MVVM or not.

Passing argument from fragment to activity using SafeArgs

Using navigation graph, when i navigate from fragment to activity and also passing argument using safeargs, in activity, I cannot get argument. How can I get argument passing from fragment???
In fragment, I can get argument by getArgument() function, but not in activity.
In fragment I switch to another activity by:
findNavController().navigate(AFragmentDirections.actionAFragmentToBActivity(1)
and in B activity get argument in onCreate by :
val args = BActivityArgs.fromBundle(savedInstanceState!!)
but my app crash immediately.
The accepted answer is not an answer to your question. As you point out: you cannot use getArguments() in your Activity, you can only do that in a fragment. However, in an activity you can get the data like this (java syntax):
String aField = BActivityArgs.fromBundle(getIntent().getExtras()).getAField()
So, just replace getArguments() with getIntent().getExtras() if you have an Activity on the receiving end.
Check it out the Android Doc :-
https://developer.android.com/guide/navigation/navigation-pass-data#java
Send Data
#Override
public void onClick(View view) {
EditText amountTv = (EditText) getView().findViewById(R.id.editTextAmount);
int amount = Integer.parseInt(amountTv.getText().toString());
ConfirmationAction action =
SpecifyAmountFragmentDirections.confirmationAction()
action.setAmount(amount)
Navigation.findNavController(view).navigate(action);
}
Get Data :-
#Override
public void onViewCreated(View view, #Nullable Bundle savedInstanceState) {
TextView tv = view.findViewById(R.id.textViewAmount);
int amount = ConfirmationFragmentArgs.fromBundle(getArguments()).getAmount();
tv.setText(amount + "")
}
BActivityArgs.fromBundle(getIntent().getExtras()).getAField();
Work perfectly
As stated in the official documentation HERE :
The Navigation component is designed for apps that have one main activity with multiple fragment destinations. The main activity is associated with a navigation graph and contains a NavHostFragment that is responsible for swapping destinations as needed. In an app with multiple activity destinations, each activity has its own navigation graph
A solution might be: rethink if the activity could be converted to a Fragment and then the newly created Fragment could be handled by the same Navigation Component. Thus allowing you to use the normal SafeArgs syntax to pass and retrieve data.
If you are still having problems with the SafeArgs plugin, I would highly recommend this medium article by the official Android team, HERE

Get fragment from NavController

I have one NavController (navOrdersController) with 2 fragments inside. My current fragment is OrderDetailFragment and I want access to first Fragment (OrdersFragment) to update data in MainActivity. How can I do this?
I tried findFragmentByTag and findFragmentByID but always return null.
I got access OrderDetailFragment with:
this.supportFragmentManager.fragments[position].findNavController().currentDestination
But I need previous fragment. Thank you very much
Can't say the code snippet (I've added below) is the best solution, but it worked for me to get the recent/current fragment added in NavHostFragment used in Android Navigation Component's implementations
OR
You can even override onActivityResult of the you activity with your fragment's, all you need is to get the added Fragment and call it's onActivityResult, using this code snippet:
NavHostFragment navHostFragment = (NavHostFragment) getSupportFragmentManager().findFragmentById(R.id.nav_host_fragment);
Fragment fragment = navHostFragment.getChildFragmentManager().getFragments().get(0);
fragment.onActivityResult(requestCode, resultCode, data);
If both the fragments have same parent activity, i.e. use getActivity() to get activity reference and use Activity.getSupportFragmentManager to find the fragment.
.....
Activity X -> Fragment A -> Fragment B
If fragment B is child of Fragment A, use getParentFragment. The fragment you are looking for would be this fragment.
Let me know if anything is not clear.
PS: Jetpack Navigation library does the same thing which we have been doing explicit. So, the previous logic of Activity-Fragment is still valid.
EDIT:
try out (parentFragment as NavHostFragment).childFragmentManager.findFragmentById()
I forgot last time that activity has declaration of NavHostFragment in its layout so there will always root parent fragment of type 'NavHostFrament'
EDIT:
I have found a work around for it
parentFragment.childFragmentManager.getFragment(Bundle().also { it.putInt("foobar",0) }, "foobar")

Navigation Component .popBackStack() with arguments

I have Two fragment. SecondFragment and ThirdFragment. Actually I use the Navigation Component for passing value between fragments. Like this:
SecondFragment:
val action = SecondFragmentDirections.action_secondFragment_to_thirdFragment().setValue(1)
Navigation.findNavController(it).navigate(action)
Here is how I read the value from the ThirdFragment:
arguments?.let {
val args = ThirdFragmentArgs.fromBundle(it)
thirdTextView.text = args.value.toString()
}
It's work fine. Now my stack is look like this:
ThirdFragment
SecondFragment
There is any option for pass value from the opened ThirdFragment to the previous SecondFragment with the new Navigation Component? (When ThirdFragment is finishing)
I know about onActivityResult, but If Nav.Component serve better solution than I want use that.
Thank you!
It's a bit late for this answer but someone may find it useful. In the updated versions of the navigation component library it is now possible to pass data while navigating back.
Suppose the stack is like this
FragmentA --> FragmentB.
We are currently now in FragmentB and we want to pass data when we go back to FragmentA.
Inside FragmentAwe can create an observer with a key:
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
val navController = findNavController()
// Instead of String any types of data can be used
navController.currentBackStackEntry?.savedStateHandle?.getLiveData<String>("key")
?.observe(viewLifecycleOwner) {
}
}
Then inside FragmentB if we change its value by accessing previous back stack entry it will be propagated to FragmentA and observer will be notified.
val navController = findNavController()
navController.previousBackStackEntry?.savedStateHandle?.set("key", "value that needs to be passed")
navController.popBackStack()
Just came across setFragmentResult(), pretty easy to use. The docs on this are here.
If you are navigating: Fragment A -> Fragment B -> Fragment A
Add this to fragment A:
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setFragmentResultListener("requestKey") { requestKey, bundle ->
shouldUpdate = bundle.getBoolean("bundleKey")
}
}
Then in fragment B add this line of code:
setFragmentResult("requestKey", bundleOf("bundleKey" to "value to pass back"))
// navigate back toFragment A
When you navigate back to fragment A the listener will trigger and you'll be able to get the data in the bundle out.
What you are asking for is an anti-pattern. You should either
navigate to the second fragment again with the new values you would like to set
use the third fragment ins a separate activity and start it with startActivityForResult()
use a ViewModel or some kind of singleton pattern to hold on to your data (make sure you clear the data after you no longer need it)
these are some of the patterns that came to my mind. Hope it helps.
As described here:
When navigating using an action, you can optionally pop additional destinations off of the back stack. For example, if your app has an initial login flow, once a user has logged in, you should pop all of the login-related destinations off of the back stack so that the Back button doesn't take users back into the login flow.
To pop destinations when navigating from one destination to another, add an app:popUpTo attribute to the associated element. app:popUpTo tells the Navigation library to pop some destinations off of the back stack as part of the call to navigate(). The attribute value is the ID of the most recent destination that should remain on the stack.
<fragment
android:id="#+id/c"
android:name="com.example.myapplication.C"
android:label="fragment_c"
tools:layout="#layout/fragment_c">
<action
android:id="#+id/action_c_to_a"
app:destination="#id/a"
app:popUpTo="#+id/a"
app:popUpToInclusive="true"/>
</fragment>

Fragments, Parameters, and ActionBar 'Up' Navigation

So I am trying to get some experience with Fragments, but I'm finding some roadblocks.
My current situation is as follows.
I have an activity that displays a List whose content is determined by Extra Intent parameters sent from the 'calling' activity.
This List activity uses ListFragment declared in the XML like so:
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent" android:background="#color/black">
<fragment class="com.pixlworks.NLC.DirectoryBrowse$ListingFragment"
android:id="#+id/listing"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</FrameLayout>
Currently I get the parameter that indicates the type of content directly in the Fragment by accessing the Extra data of the Activity Intent (or saved Bundle if available):
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
if (savedInstanceState != null)
mListingType = savedInstanceState.getString(Utils.DIRECTORY_TYPE_STORE_KEY);
else
mListingType = getActivity().getIntent().getStringExtra(Utils.DIRECTORY_TYPE_STORE_KEY);
// get content by type, create and set the adapter
}
Now part of my problem is that I am not sure this is the right way to 'pass' that parameter from the Activity to the Fragment.
On top of that, I am getting issues with this setup when using the Action Bar's UP Navigation. When I click on an item in this List Activity it goes to another activity showing the details of the selected item. From this detail activity:
If I use the back button, the List Activity is brought back from the stack as usual and everything works fine.
If I use the ActionBar's UP (despite following steps here), it would seem that a new instance is created instead of using the one in the stack and this new instance obviously is not getting the Extra parameter in the Intent. Since I am expecting the value to exist in the saved Bundle or in the Intent, my app crashes in this situation.
So to boil things down, I am not sure which of these to follow and how to make them work properly with 'UP' navigation:
A) Hold the 'type' parameter in a field in the Activity and save it in the Activity's Bundle onSaveInstanceState. In which case I am not sure how to then pass the value to the Fragment. In this case I would just need to make sure that UP calls the existing instance of the Activity List
B) Continue with my current setup of saving the value in the Fragment instead of the Activity, but again, how to handle the UP navigation correctly?
I know it is kind of multiple things I am asking here at the same time, but they are all connected, so I hope that I can get some help on this.
Thanks for any help in advance!
The UP navigation makes more sense to be used within the same activity level. That is the intention of the codes that you followed in the developers page. Because you started a new activity, if you want to return to previous activity like the back button you will need to call finish() to destroy the details activity first.
As for passing data from activity to fragment, when you create a new instance of fragment, you can pass the data to it as bundle, for example:
// in fragment class
public static MyFragment newInstance(Bundle arg) {
MyFragment f = new MyFragment();
f.setArguments(arg);
return f;
}
When you create a new fragment, you can call:
// in activity
Bundle arg = new Bundle();
int info = ...;
arg.putInt("INFO",info);
...
MyFragment mFragment = MyFragment.newInstance(arg);
Finally, to get the data in fragment:
int info = getArguments().getInt("INFO");
...
Instead of directly calling MyFragment mFragment = new MyFragment() to instantiate the fragment, you should use a static method to instantiate it. This is to prevent some crashes which might happen if you rotate the screen and the framework complains that it couldn't find a public empty constructor.
UPDATE
To answer your questions:
1) Say you start from activity A -> activity B. Then in activity B you press the up button. By logic of use, the up button will not bring you back to activity A, because its intention is to navigate one level up,but still inside, activity B. To return to activity A, you need to call finish() to destroy activity B first.
2) If your fragment is created in xml, you still can set arguments. In your xml, you set an id for the fragment android:id="#+id/fragment_id", then
// in activity
FragmentManager fm = getSupportFragmentManager(); // or getFragmentManager() if you don't have backward compatibility
MyFragment mFragment = fm.findFragmentById(R.id.fragment_id);
Bundle arg = new Bundle();
// put data blah blah
mFragment.setArguments(arg);
Just make sure you set the arguments before you use the fragment.
Simply said, intent is used when you pass data between calling activities; bundle is used when you want to pass data from activity to fragment.

Categories

Resources