I have a fragment to which I pass data through a bundle. It does some processes and sends to other fragments. After that there is 2nd set of data has to be sent back to the first fragment. How can this be done?
The following code gives the error:
"IllegalStateException: Fragment already active at android.app.Fragment.setArguments(Fragment.java:696)". So there is a problem in the way the bundle is recreated. What is the correct method to do this?
Code in the activity: It passes two rows of two nested arraylists to fragments and starts the first fragment.
private void changeExercise(int x){
if(x < list.size()) {
Bundle bundle = new Bundle();
bundle.putStringArrayList("ex_list", list.get(x));
bundle.putStringArrayList("ex_data", exercise_data.get(x));
//All fragments use the same data passed from activity using the bundle
preExerciseFragment.setArguments(bundle);
//1st fragment used if !exercise_data.get(x).get(2).equals("bbb") after replacing previous
exerciseFragment.setArguments(bundle);// 2nd fragment used after replacing previous
postExerciseFragment.setArguments(bundle);//3rd fragment used after replacing previous
feedbackFragment.setArguments(bundle); //Then 4th fragment used after replacing previous
if (!exercise_data.get(x).get(2).equals("bbb")) {
getFragmentManager().beginTransaction().add(R.id.workout_layout, preExerciseFragment, "preexercise").commit();
} else {
getFragmentManager().beginTransaction().add(R.id.workout_layout, exerciseFragment, "exercise").commit();
}
} else {
Toast.makeText(this, "Workout is over for the day!! Good job!", Toast.LENGTH_SHORT).show();
}
}
Code in 4th fragment (FeedbackFragment) through an interface:
//
ex_num++;
//
changeExercise(ex_num);
This calls the changeExercise method in the activity. Now the next two rows of the two arraylists in the activity should be passed to the fragment through the bundle. This setArguments method is giving an error.
you don't need to pass data between fragments as a bundle (if they are both active at the same time).
have setters in both fragments that can assign an instance of each other (you can make these interfaces if you want) and then have preExerciseFragment.setPost(postExerciseFragment) and postxerciseFragment.setPre(preExerciseFragment); where you 'setArguments'.
Then both fragments can talk directly through method passes, no need to pack&pass data back and forth inside a bundle.
This is: less code, cleaner, and more efficient since you don't have to create extra bundle objects
Related
Imagine that you have 2 fragments connected to one (or more) viewModel(s) and inside of activity you'll switch between them. Once you open fragment, viewModel works as expected, so I start listening for changes from onCreate method, code example:
viewModel = new ViewModelProvider(requireActivity(), new InventoryTasksFactory()).get(InventoryTasksViewModel.class);
viewModel.inventoryTasksResponse().observe(this, new Observer<Response<List<InventoryTask>>>() {
#Override
public void onChanged(Response<List<InventoryTask>> listResponse) {
handleResponse(listResponse);
}
});
But when you switching to another fragment and going back, fragment becomes blank. I understand that fragment listening changes inside of viewModel, and you should manually getting value from viewModel and I get value from viewModel inside of onCreateView method, code example:
Response<List<InventoryTask>> inventory = viewModel.inventoryTasksResponse().getValue();
if (inventory!=null){
handleResponse(inventory);
}
Problem is that Response has 3 states: Running, Success, Error, and depends on those states view is updating. So, in first fragment opening, view updating twice and it leads to skipping frames and display blinking.
I was thinking about keeping data inside of fragment, but I want to avoid data duplicating. Besides of that, in case of sharedViewModel, you'll get issues about updating data inside of fragment!
Please, help me!
Observing your data from onViewCreated(View view, Bundle savedInstanceState) might work out.
My android application have BottomNavigationBar. It contains 5 fragments. One of its fragment contain nested fragment which is mutiple step process.
first nested fragment contain next button.Second nested fragment contain previous and next button.Third nested fragment contain previous and submit button. Each fragment have different EditText.
After adding the values in first fragment, when i click next button it goes to second fragment. In second fragment when i click previous button it goes to first fragment again and same process applies to second and third fragment
My questions is:
1)When previous button in second fragment is clicked, i want all the values of EditText in first fragment as it is and when again next button of first fragment is clicked, i want all the values of EditText in second fragment as it is. Is there any way to do this?
2)I want all the EditText values of all nested fragments when user clicked on submit button in third fragment.How to do that?
Yes,
This can be achieved using two ways,
1) Fragment savedInstanceState
https://stackoverflow.com/a/17135346/7316675
2) Keep you values stored at some activity or application level, and access it on resume of fragment screen
You could use a Bundle to store the values and then restore them when you restore the fragment. You could either do this in onStop() (recommended) or onPause().
Private static final String KEY_ADDRESS = "ADDRESS" ;
#Override
Public void onstop(){
Bundles state = new Bundle ();
String address = etv1.getText().toString();
// Get more strings from the etvs
state.putString(KEY_ADDRESS, address);
// Store more strings into the bundle
setInitialSavedState(state)
}
To restore the values you use either the saved instance state the system passed to onCreate() , or pass the bundle to a self created public bundle in onCreate() and access in onResume() like so:
String address = bundle.getString(KEY_ADDRESS);
As for the results being passed you could communicate the bundles to their parent activity whenever the next or previous button is pressed and do with it as you please when submit is pressed. Learn more on How to do that from the docs or this answer on how to do that
Solution of my first question.
I have used popBackStack() method of FragmentManager. Using this i can go back to the previous fragment of stack.I have added this code in OnClickListener of previous button
FragmentManager fm = getActivity().getSupportFragmentManager();
if(fm.getBackStackEntryCount()!=0){
fm.popBackStack();
}
Solution of my second question:
use method setArguments() to set the values and getArguments() to get the values
I want to be able to define a listener (an Activity, Fragment, etc) and be able to pass it through any number of nested Fragments before I decide to finally invoke the callback. That way it can call callback.someFunction() and it doesn't need to know what Activity or Fragment that callback is attached to.
Right now it seems, though, that there is no good way to send a listener through a bunch of Fragments. I initially considered passing it through the constructors, but then the listener reference would be nulled out on a configuration change like a screen rotation.
I then considered the onAttach() methods, but these only give you access to the context of the base Activity which doesn't necessarily do what I want, either.
I also considered passing the listeners in through newInstance() (which is normally how you save arguments passed into Fragments because the contents of getArguments() survives configuration changes via the Bundle), but I could not see any good way to save the listener in the argument Bundle.
What can I do?
What you should do is recreate everything when a configuration change happens or when the activity is re-created and its instance state restored...which is the same for that matter...
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.acitivyt_layout);
if(savedInstanceState != null){
//1. restore instance state here
savedInstanceState.getString("whatever");
//2. try to find the fragment
Fragment f = getSupportFragmentManager().findFragmentByTag("FRAGMENT TAG");
//3. make sure the fragment is correctly set up if already loaded
if(f != null){
((ConcreteFragmentType)f).setListener(your_listener);
//bring the fragment to the front or show it using the fragment manager
}
//4. or else add a brand-new instance
else{
ConcreteFragmentType c = ConcreteFragmentType.newInstance();
c.setListener(your_listener);
getSupportFragmentManager().beginTransaction()
.add(R.id.fragmentContainer, c)
.commit();
}
}
}
Here is my problem area:
I have a Fragment A. Once it is attached, in its onCreateView, I load a webservice to fetch the data from the server and after that I set that data on the list view using a Base Adapter. Now on the Item Clicks of the list view I replace the Fragment A with Fragment B using replace Methods of the Fragment Transactions and addtoBackstack("FragmentA").
FragmentManager fm =getActivity().getFragmentManager();
fm.beginTransaction().replace(R.id.content_frame, Fragment B).commit();
Now here when I press back button on Fragment B, it takes me to Fragment A but the webservice again starts loading.
My Problem: I just want that when it returns to Fragment A, it should show its previous state and should not call the webservices again.
Thanks
OnCreateView for a fragment runs on the creation of the view every time it needs to be drawn. By going back you are causing the view to be recreated and hence the webservices are loading again.
I believe that if you only want the web services to load once then you could move the code to the "onCreate" method instead, but its probably a better idea to move this code to "onResume" instead and include some logic that checks whether you need to load your webservices again or not.
This way everytime the fragment is paused and then loaded again you could ensure that the fragment still has everything it needs.
(source: xamarin.com)
EDIT:
So for example you could have
#Override
public void onResume() {
super.onResume(); // Always call the superclass method first
if (data == null) { //Or list is empty?
getWebData()
}
}
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.