It seems like a duplicate question but actually the answers in similar question don't address my problem: I'm making a game (slide puzzle) where the state (array of numbers) of the board is important. So, when orientation changes I want to save this array from fragment 1 (which will be on screen only in portrait mode) and pass it to fragment 2 (which will be on screen only in landscape mode). So far I managed to get this (in onCreate of the activity that holds the fragments):
GameFragment gameFragment = new GameFragment();
gameFragment.setArguments(getIntent().getExtras());
GameFragment9 gameFragment9 = new GameFragment9();
gameFragment9.setArguments(getIntent().getExtras());
if (getResources().getConfiguration().orientation == Configuration.ORIENTATION_PORTRAIT){
getSupportFragmentManager().beginTransaction()
.replace(R.id.container, gameFragment, "fragmentGame")
.commit();
}
else if (getResources().getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE){
getSupportFragmentManager().beginTransaction()
.replace(R.id.container, gameFragment9, "fragmentGame9")
.commit();
}
Also I learnt how to save the state of one fragment but this is not exactly what I want because I don't want the last fragment to be rebuild, just put a new fragment with information from the first fragment. I was thinking about using onConfigChange to detect when the screen changed and there do something, but don't know how to do that something.
ViewModel can be answer for your question. Create list of numbers and wrap it with LiveData (optional). Get numbers via ViewModel and save them via ViewModel. Inside onViewCreated set text from ViewModel. Like
textView.text = viewModel.numbers
Only important thing is exactly same ViewModel should be used inside
activity and its child fragments
you can achieve this by providing same owner to ViewModel during initialization time of ViewModel
Related
I am trying to enable user to go to a new Fragment when a list item is clicked. That's OK. I created an interface which allows me to handle click events from my FragmentA.java class. FragmentA is attached to my activity when activity started. my activity extends FragmentActivity.
In my activity class:
#Override
protected void onCreate(Bundle savedInsantaceState){
//...
getSupportFragmentManager().beginTransaction().replace(R.id.container, FragmentA.newInstance(param1, param2)).commit();
}
And then in FragmentA.java, i set that to my RecyclerView Adapter as click handler. I use add() method instead of replace() method to change the fragment, because i want to save the FragmentA's state (like RecyclerView position etc.) when FragmentB is attached.
private void setListeners(){
mAdapter.setOnItemClickListener(itemClickListener);
}
private ItemListAdapter.ItemClickListener itemClickListener = new ItemListAdapter.ItemClickListener() {
#Override
public void onItemClicked(View v, ItemModel item) {
FragmentManager manager =((FragmentActivity)mActivity).getSupportFragmentManager();
manager.beginTransaction().add(R.id.post_activity_layout_container, FragmentB.newInstance(item, param2, param3)).addToBackStack("comment").commit();
}
};
HERE IS THE ISSUE : In this case, FragmentA is running but invisible, while user sees FragmentB. User can reach views of FragmentA, and that cause problems. I wanna save the last state of FragmentA but user should not click on views of FragmentA from FragmentB. How to handle that issue? Is there a better practice to accomplish saving the last state?
EDIT
FragmentA contains some sorting, filtering. When i use replace() method, all filters that user set is invalidated, and also RecyclerView position became 0. Imagine that user is looking at (for example) 33. item in the list, clicks on it, FragmentB is attached, then go back to FragmentA. I want user to continue from 33. item, don't want user to try to find for where he was.
I'm not sure what you mean by "I wanna save the last state of FragmentA" exactly, but, AFAIK, the fact that you replace Fragments in a container doesn't mean they lose state. For example, you can still click on back button and this will revert the transaction, bringing the previous Fragment from the back-stack.
Edit: the effects that you observe are most probably caused by the destruction and re-creation of Fragment's View hierarchy. There are couple of approaches around it. The first one would be to store UIs state and restore it after re-initialization of the Fragment. Unfortunately, it might be tricky with RecyclerView position (you can google it). Another simpler approach (which is a hack) is to create the root View in onCreateView only once, keep a reference to it inside Fragment and return the same View on subsequent calls to onCreateView. If you decide to use the later approach, be careful because you'll be using Fragments not exactly the way there were intended to use.
Not directly related to your question, but I absolutely recommend avoiding manual Fragments management. It'll be too painful. You can use the official Navigation Component, or, alternatively, a simpler solution like FragNav library. I wrote this post about the later and it might help you.
I have an activity:
public class MainActivity extends FragmentActivity
{
private FragmentA a;
private FragmentB b;
private FragmentC c;
private HomeFragment mHomeFragment;
#Override
protected void onCreate(#Nullable Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.home_activity_layout);
FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
getSupportFragmentManager()
.beginTransaction()
.replace(R.id.fragment_container, mHomeFragment) // replace flContainer
.addToBackStack(null)
.commit();
...
}
I use some lazy initializer, and I keep my object a, b, c and mHomeFragment inside my activity object,
I have a button inside 'b' that calls the MainActivity to call 'c'
and a button inside 'c' that calls the MainActivity to call 'b'
and some other buttons to call 'a' which is not that important
Well now inside my FragmentB I have some custom views,
public CustomView(Context cnx, SettingsViewElement e, FragmentA fragment)
{
super(cnx);
inflate(cnx, R.layout.my_layout,this);
parent = fragment; // I use it later in my onCheckedChangeListener to tell him the the switch has been checked
condition = parent.getCondition();
titleTv = this.findViewById(R.id.title_tv);
switch = this.findViewById(R.id.switch);
if(condition){
switch.setChecked(true);
titleTv.setTitle("i am depressed af :(");
}
my question is :
why my switch doesn't turn checked even if my titleTv is updating the title ??
I did some tests and my OnCreated() is called each time I call replace() in my fragment transaction.
my fragment re-instantiate the views and then will add them to a linearLayout after inflating layout.
Also, if I don't keep my fragments reference in my MainActivity and then re-instantiate it every time.
here : the switch.setChecked will work ,
but the backStack will be huge and dumb
example: stack = 'b' -> 'c' -> 'b' -> 'c'
normally when the user click twice return button he'll be back to homeFragment, it doesn't need to empty the stack by itself.
and even if I override #onBackPressed() and re-instantiate the fragments each time hoping the android sdk will free the space.
I will have to do it manually each time I want to add a new fragment in my design, it will be a a wheel re-invention.
and : IT DOES NOT EXPLAIN WHY THE Switch.setChecked() doesn't work even if the condition is true and the other views are updating (textView is updating it's text)
Update : Apparently,
if the fragment is being re-used and the onCreate() is called for the
second time, (the reference for the fragment is being kept somewhere
and fragment is being attached for a second time) here the setChecked
will not take an effect, only some other view updates
meanwhile, it will only work if it's being called for the first time
the fragment is being created,
Solution : updating the views in the OnResume() method
If someone has an explanation for this, please go ahead
Update :
Apparently,
if the fragment is being re-used and the onCreate() is called for the seconde time, (the reference for the fragment is being kept somewhere and fragment is being attached for a seconde time)
here the setChecked will not take an effect, only some other view updates
meanwhile, it will only work if it's being called for the first time the fragment is being created,
Solution :
updating the views in the OnResume() methode
If someone has an explanation for this,
please go ahead
Views save and restore (some) of there state in the course of saveInstanceState/restoreInstance cycle, but unfortunatrly the checked state (and also visibility etc) are not covered by that.
So you'll have implement onSaveInstanceState etc. for your fragment and save/restore the checked state yourself.
EDIT
From your question it is not apparent what view switch actually is so I cannot give you actual code. But have a look at the implementation of CheckedTextView and check out the implementation of onSaveInstanceState() and onRestoreInstanceState(). You'll see that they save and restore the checked state (it's quite a lot of code with the SavedState class but in the end it is pretty simple). What you need to do, is to implement those two methods for your custom view (also creating your own SavedState inner class) and save/restore the checked state of your switch view.
A completely different and better approach would be to use a ViewModel for your fragment that holds the state, but that's a different story that would require lots of changes to your complete implementation.
I am having some problems to understand the differences between Activity and Fragment.
I have done an activity called "PublicarActivity" and a Fragment called "PublicarFragment".
They have exactly the same code (with some differences to work as a fragment and as an activity) so that is not a problem.
My problem is that I do not really know how to work with "onBackPressed". I know that before than calling the fragment, you should add it to the stack, but right now I would like to do something a little bit more complicated.
This is the code for my Activity's onBackPressed:
#Override
public void onBackPressed() {
if(layout_activado){
verificable.toggle();
verificar_layout.setVisibility(View.INVISIBLE);
layout_activado = false;
pulsado = false; }
else{
Intent intent_cancelar = new Intent(PublicarActivity.this, Principal_Activity.class);
startActivity(intent_cancelar);
}
}
How could I do exactly this from my fragment?
There are two things in your question to be solved to get you the answer.
First thing is confusion between Activity and Fragment. You might have encountered an statement -"Activity represents single screen" in Android. So having Activity in your application will let your user interact with various views such as buttons, lists etc. So now, let's consider an instance when you want to add such a view in your Activity which should contain some state lifecycle (like you can have list in fragment and clicking on item should lead you to detailed view in the same view) so that you can have mini-Activity in your main activity while all other components remaining at the same positions. So providing functionalities like mini-activity your Fragment is going to have some life-cycle methods which will be called during Fragment Life time. So you can use Fragment whenever you feel you want some sub-Activity in your main Activity or for any other use. You can cover your whole Activity with Fragment as we mostly do whenever we want to have Navigation-Drawer in our app.
Now that you have got clear about Fragment and Activity( I hope so) you can refer to the link provided by person named cricket which is this.
In a master detail flow, when I go from landscape to portrait, my detail fragment is still there.
What's the best place and time (lifecycle callback) to get rid of it? I only have to get rid of it because my menu items and actionbar title are coming from the detail fragment, in portrait mode, and so it doesn't make any sense.
In the onCreate method of your activity, you could try that:
DetailFragment detailFrag = getFragmentManager().findFragmentByTag
if( <your logic to see if portrait> && detailFrag !=null && detailFrag.isVisible()){
<Remove the Fragment using a normal fragment transaction>
}
Short answer would be onCreate
And there are multiple things you would need to know
When screen orientation change, which is one of the configuration changes, which will trigger Activity recreation.
You can stop Activity recreation by setting onConfigChange, but do NOT do this unless this is a special app, e.g. Camera
Activity recreate is trying to help you, and your case is exactly where it is needed, and you can do the correct setup in onCreate
There are multiple ways to handle the different orientation, the basic way would be to use different layout, e.g. for your layout, say activity_main.xml, you can define a similar xml with the same name, under layout-land folder. When you setContentView(R.layout.activity_main), Android will select the right layout for you depending on your configuration.
In your case, you are switching between 2 fragments and 1 fragment, there are additional works you need to handle for the fragment transaction. However, this would depends on your existing FragmentTransaction and this will go too broad to go on, so I will stop here.
When adding activities to the stack, I can do something like this suggests:
How to bring an activity to foreground (top of stack)?
However, I have a Navigation Drawer that uses fragments. I add these fragments to my back stack via the below code:
FragmentTransaction transaction = activity.getFragmentManager().beginTransaction();
transaction.replace(R.id.main_fragment, new EntryFragment());
transaction.addToBackStack(activity.mTitle.toString());
transaction.commit();
The problem is, I now need to take a fragment that is already part of the back stack and bring it to the top, dropping all fragments currently above it out of the stack. Essentially what FLAG_ACTIVITY_REORDER_TO_FRONT and FLAG_ACTIVITY_CLEAR_TOP flags would do when using activities.
How do I accomplish this with fragments?
You can use the following method to return to the instance of the Fragment on the backstack:
activity.getFragmentManager().popBackStackImmediate(tag, 0);
Note, that in your FragmentTransaction you will need to define a unique tag for each Fragment you commit to the backstack and retrieve that tag to return to the fragment here.