Android fragment order after orientation changed - android

I have one activity with many fragments.
If I call fragment B from fragment A(A background, B foreground) and then change the orientation of my device, fragments are showed in different order: A is in foreground and B in background.
If I press back, fragment B is detached so I assume that the position into the backstack is fine.
How could I restore fragments in the right order?
I can't use android:configChanges="orientation|screenSize"
Thanks

By your post, I found that I have the exact problem as you.
The fragment will reload when the configuration change, so when the orientation change. Therefore you need to realod the view.
I used the post to find the solution.Change fragment layout on orientation change!
Here is the solution that works for me. Hope it will help you.
public class YourFragmentActivity extends Fragment {
private FrameLayout frameLayout;
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
frameLayout = new FrameLayout(getActivity());
LayoutInflater inflater2 = (LayoutInflater)getActivity().getSystemService(Context.LAYOUT_INFLATER_SERVICE);
frameLayout.addView(ReloadView(inflater2));
return frameLayout;
}
#Override
public void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
frameLayout. removeAllViews();
LayoutInflater inflater = (LayoutInflater)getActivity().getSystemService(Context.LAYOUT_INFLATER_SERVICE);
frameLayout .addView(ReloadView(inflater));
}
private View ReloadView(LayoutInflater inflater) {
View v = inflater.inflate(R.layout.your_layout, null);
//do your staff, button, listener, etc
return v;
}
}
And dont forget to add that in you manifest, otherwise it will not work.
<activity android:name="com.example.YourFragmentActivity"
android:label="#string/app_name"
android:windowSoftInputMode="stateHidden"
android:configChanges="orientation|screenSize"/>

Related

Layout change when orientation changes in Android Fragments

please suggest a solution.
When i rotate my fragment it should change in to landscape mode and to display another layout.But screen is not rotating to landscape.
My code blow:
<activity
android:name=".activites.MainActivity"
android:launchMode="singleInstance"
android:configChanges="keyboardHidden|screenLayout|screenSize|orientation"
/>
This is main layout called dashboard and now it is in portrait mode:
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View view=View.inflate(getContext(), R.frag_dashboard,null);
changeview= (ShimmerTextView)view.findViewById(R.id.changeview);
return view;
}
when i rotate the screen this fragment changed to landscape mode and set another layout, and prayer_times is the new layout.
#Override
public void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
// Checks the orientation of the screen
if (newConfig.orientation == Configuration.ORIENTATION_LANDSCAPE) {
Toast.makeText(getContext(), "landscape", Toast.LENGTH_SHORT).show();
view=View.inflate(getContext(), R.layout.prayer_times,null);
}
}
and i create layout_land for prayer_times
If your fragment has no issue of reloading when orientation change you can simply reload.
Add two layout with same name in layout and layout-land folders.
This will show correct oriented layout when load, for change layout when device rotate
add following in onConfigarationChanged method inside fragment itself.
#Override
public void onConfigurationChanged(Configuration newConfig){
super.onConfigurationChanged(newConfig);
if (newConfig.orientation == Configuration.ORIENTATION_LANDSCAPE || newConfig.orientation == Configuration.ORIENTATION_PORTRAIT){
try {
FragmentTransaction ft = getFragmentManager().beginTransaction();
if (Build.VERSION.SDK_INT >= 26) {
ft.setReorderingAllowed(false);
}
ft.detach(this).attach(this).commit();
} catch (Exception e) {
e.printStackTrace();
}
}
}
If the onCreateView function is called when you rotate the screen, you can do this in it:
if(this.getResources().getConfiguration().orientation==Configuration.ORIENTATION_LANDSCAPE) {
......
} else if(this.getResources().getConfiguration().orientation==Configuration.ORIENTATION_PORTRAIT) {
.........
}
Late but this will help some one
Try this in V4 Fragment
#Override
public void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
if (getFragmentManager() != null) {
getFragmentManager()
.beginTransaction()
.detach(this)
.attach(this)
.commit();
}
}
What you're trying to do is rather complicated. Android Fragments are not meant to be rotated.
I had the same problem and found a solution, though. In my case, I wanted to present a Fragment containing different menu pages that would rotate according to orientation.
Just create a Fragment that serves as a base and contains a simple LinearLayout (or any other layout type you want). This LinearLayout will serve as the canvas for our menu:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/llMenuCanvas"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
</LinearLayout>
Next, we want to code the base item fragment as an abstract class, that will be implemented by all menu item fragments:
public abstract class NavMenuItem extends Fragment {
static final String TAG = "yourTag"; // Debug tag
LinearLayout canvas;
View hView; // we'll keep the reference of both views
View vView;
// All we'll need to do is set these up on our fragments
abstract int getVerticalLayoutResource();
abstract int getHorizontalLayoutResource();
abstract void setupUI(); // assigns all UI elements and listeners
#Nullable
#Override
public View onCreateView(#NonNull LayoutInflater inflater, #Nullable ViewGroup container, #Nullable Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.menu_base, container, false); // sets up the layout for this fragment
// keeping our references to both layout versions
hView = inflater.inflate(getHorizontalLayoutResource(), container, false);
vView = inflater.inflate(getVerticalLayoutResource(), container, false);
canvas = view.findViewById(R.id.llMenuCanvas); // this is the magic part: Our reference to the menu canvas
// returning our first view depending on orientation
if (getResources().getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE){
canvas.addView(hView);
}else{
canvas.addView(vView);
}
return view;
}
#Override
public void onViewCreated(#NonNull View view, #Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
setupUI(); // here we set up our listeners for the first time
}
// Here we update the layout when we rotate the device
#Override
public void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
canvas.removeAllViews();
// Checking screen orientation
if (newConfig.orientation == Configuration.ORIENTATION_LANDSCAPE) {
canvas.addView(hView);
}
else if (newConfig.orientation == Configuration.ORIENTATION_PORTRAIT){
canvas.addView(vView);
}
setupUI(); // we always need to rebind our buttons
}
}
And here is an example of a menu item fragment that rotates according to the device's orientation.
public class NavMenuMain extends NavMenuItem{
static final String TAG = "yourTag"; // Debug tag
// Your layout references, as usual
ImageButton btnCloseMenu;
// here we set up the layout resources for this fragment
#Override
int getVerticalLayoutResource() { // vertical layout version
return R.layout.menu_main_port;
}
#Override
int getHorizontalLayoutResource() { // horizontal layout version
return R.layout.menu_main_land;
}
#Override
void setupUI(){
// Setup button listeners and layout interaction here
// REMEMBER: the names of your layout elements must match, both for landscape and portrait layouts. Ex: the "close menu" button must have the same id name in both layout versions
}
}
Hope it helps.
All you need to do is open a new layout-land folder inside your res folder and put there xml with the same name of your fragment's layout, the framework will know to look for that .xml on orientation changed.
Look here for details.
By default, the layouts in /res/layout are applied to both portrait and landscape.
If you have for example
/res/layout/main.xml
you can add a new folder /res/layout-land, copy main.xml into it and make the needed adjustments.
See also http://www.androidpeople.com/android-portrait-amp-landscape-differeent-layouts and http://www.devx.com/wireless/Article/40792/1954 for some more options.
When you change the orientation, your fragment destroyed and recreated again (See this for better understanding). So in onConfigurationChanged, you inflate your new layout but it's useless because when your fragment recreated, the onCreateView is called again; in other words, your old layout is inflated again. So better to do this in this way:
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View view;
if(getActivity().getResources().getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE) {
view = View.inflate(getContext(), R.frag_dashboard,null);
changeview = (ShimmerTextView)view.findViewById(R.id.changeview);
} else(getActivity().getResources().getConfiguration().orientation == Configuration.ORIENTATION_PORTRAIT) {
Toast.makeText(getContext(), "landscape", Toast.LENGTH_SHORT).show();
view = View.inflate(getContext(), R.layout.prayer_times,null);
}
return view;
}

How can I maintain a child view's state when switching fragments?

I am having a hard time understanding how the fragment lifecycle relates to switching between fragments in the back stack. Please bear with me if my question exposes more than one misconception.
Here is my code:
public class SomeFragment extends Fragment {
private SomeCustomView customView;
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.some_fragment, container, false);
return view;
}
#Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
// Create the child view
customView = (SomeCustomView) getView().findViewById(R.id.some_fragment_child_view);
customView.initializeMyCustomView();
}
}
As you can see, my fragment has a child view. The child view is a custom one. Here's code:
public class SomeCustomView extends SurfaceView implements SurfaceHolder.Callback {
private boolean aVariableWhichMustPersistForLifetimeOfApplication;
}
Whenever this fragment is added to the back stack and then later restored, the variable customView is recreated, and so I loose the value of aVariableWhichMustPersistForLifetimeOfApplication. This is creating all sorts of problems for me.
The application started out using an Activity that only displayed SomeCustomView and there were no fragments. Now I have to add functionality and so I have turned the custom view into a fragment, and thus I arrive at this problem.
I found an answer which works for me. The FragmentTransaction class has a number of methods which allow you to switch fragments in/out. (Android documentation for FragmentTransaction is here and a great StackOverflow explanation is here.)
In my case, I wanted SomeFragment to never loose the data contained in its view. To do this, use this code:
SomeFragment fragment = new SomeFragment();
FragmentTransaction transaction = getFragmentManager().beginTransaction();
transaction.add(R.id.activity_fragment_placeholder, fragment, "some_fragment");
transaction.commit();
and then later:
getFragmentManager().beginTransaction().hide(fragment).commit();
You can now add/attach a different fragment to R.id.activity_fragment_placeholder. Notice that I'm using hide() rather than replace(), that's the key difference that keeps the view from being destroyed. When you want the fragment back, you can use show() or Android will do this automatically when the user clicks "Back" if you use addToBackStack() when adding/attaching your other fragment.

fragments and onConfigurationChanged

I'm trying to do something I do with activities, but within a fragment.
What I do is using activities:
First stop the activity restarts when rotating the device
android:configChanges="keyboardHidden|orientation|screenSize"
in my activity add:
#Override
public void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
setContentView(R.layout.main);
}
So get the activity does not restart, but reloading the main.xml, to use the layout-land
Now I have an activity showing viewpager, which contains three fragments.
Everything works properly. Detection of the rotation is in the fragments
public class FRG_map_web extends Fragment {
#Override
public void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
Log.i("myLogs", "Rotation");
}
The problem is that the fragment not use setContentView(R.layout.main); this is the code:
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
view = inflater.inflate(R.layout.frg.myFragment, null);
I tried to use:
LayoutInflater inflater = inflater = (LayoutInflater) getApplicationContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE);
view = inflater.inflate(R.layout.frg.myFragment, null);
...
LayoutInflater vi = (LayoutInflater) getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE);
view = inflater.inflate(R.layout.frg.myFragment, null);
...
LayoutInflater inflater = (LayoutInflater) context.getSystemService( Context.LAYOUT_INFLATER_SERVICE );
view = inflater.inflate(R.layout.frg.myFragment, null);
...
LayoutInflater li = LayoutInflater.from(context);
and different ways, but always without success
I can not inflate properly.
Can anyone tell me how I have to do?
Thanks in advance, I appreciate the help
Regards
If I understand correctly, you don't want to the fragment to reload on each rotation. Instead you want to relayout the views and update them with information you already have.
public void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
// Get a layout inflater (inflater from getActivity() or getSupportActivity() works as well)
LayoutInflater inflater = (LayoutInflater) getApplicationContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE);
View newView = inflater.inflate(R.layout.frg.myFragment, null);
// This just inflates the view but doesn't add it to any thing.
// You need to add it to the root view of the fragment
ViewGroup rootView = (ViewGroup) getView();
// Remove all the existing views from the root view.
// This is also a good place to recycle any resources you won't need anymore
rootView.removeAllViews();
rootView.addView(newView);
// Viola, you have the new view setup
}
According to the documentation (getView()), getView() returns the same view that you returned from your onCreateView() but it does not. It actually return the parent of the view you returned in onCreateView() which is exactly what you need. getView() will return an instance of NoSaveStateFrameLayout, which is used specifically for Fragments as its root view.
Hope this helps.
Have you considered retaining your fragment instances? See Fragment#setRetainInstance.
Allow your Activity to be recreated (do not specify android:configChanges) but retain your fragment instances across orientation changes. If all the heavy lifting happens in Fragment#onCreate this should work fine. onCreate() will not be called again since the fragment is not being re-created.
I could do it by re-attaching the fragment within onConfigurationChanged:
#Override
public void onConfigurationChanged(Configuration newConfig)
{
getActivity().detachFragment(this);
super.onConfigurationChanged(newConfig);
....
getActivity().attachFragment(this);
}
Remember that by detaching and attaching your fragment you will be only working with its view. But the fragment state is "saved" in the Fragment manager.
You can try to detach e attach the fragment:
#Override
public void onConfigurationChanged(Configuration newConfig) {
FragmentManager fragmentManager = getFragmentManager();
if (fragmentManager != null) {
fragmentManager.beginTransaction().detach(this).commitAllowingStateLoss();
}
super.onConfigurationChanged(newConfig);
if (fragmentManager != null) {
fragmentManager.beginTransaction().attach(this).commitAllowingStateLoss();
}
}
Maybe you can try use
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
view = inflater.inflate(R.layout.frg.myFragment, container, false);
}
. In any case, the fragment still has to be destroyed and recreated, why not let Android handle it automatically by restarting the activity? If there is any data to keep, you can save it in onSavedInstanceState(). Setting android:configChanges="keyboardHidden|orientation|screenSize" is not recommended in Android.

Change Fragment layout on orientation change

I have the following problem:
I have a TabActivity that shows a FragmentActivity in one of its tabs.
That FragmentActivity adds a ListFragment, when clicked on the item of that ListFragment, a Fragment is added (also to the backstack) and displayed.
Now I need to change the layout of that Fragment to change when going to landscape orientation.
But I'm totally clueless where to implement that change. I have already created to correct layout in the layout-land folder. But where is the correct point to set it?
Warning: this may be a pre-Lollipop answer.
A Fragment doesn't get re-inflated on configuration change, but you can achieve the effect as follows by creating it with a FrameLayout and (re)populating that manually:
public class MyFragment extends Fragment {
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle bundle) {
FrameLayout frameLayout = new FrameLayout(getActivity());
populateViewForOrientation(inflater, frameLayout);
return frameLayout;
}
#Override
public void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
LayoutInflater inflater = LayoutInflater.from(getActivity());
populateViewForOrientation(inflater, (ViewGroup) getView());
}
private void populateViewForOrientation(LayoutInflater inflater, ViewGroup viewGroup) {
viewGroup.removeAllViewsInLayout();
View subview = inflater.inflate(R.layout.my_fragment, viewGroup);
// Find your buttons in subview, set up onclicks, set up callbacks to your parent fragment or activity here.
}
}
I'm not particularly happy with the getActivity() and related calls here, but I don't think there's another way to get hold of those things.
Update: Removed cast of ViewGroup to FrameLayout and used LayoutInflater.from(), and the third parameter of inflate() instead of adding the view explicitly.
I believe that if you have layouts that are for specific device orientations then all you need do is give them the same name but place them in the appropriate resource directory. This link gives some explanation. The Android system then takes care of selecting the appropriate resource but you can handle this yourself if needs be.
You need two different xml designs with the same name within the layout and layout-land packages under the res package.
When the orientation changes, override the onConfigurationChanged() function and edit the function as follows to load the xml file suitable for the orientation.
override fun onConfigurationChanged(newConfig: Configuration) {
val fragmentManager: FragmentManager = requireActivity().supportFragmentManager
fragmentManager.beginTransaction().detach(this).commitAllowingStateLoss()
super.onConfigurationChanged(newConfig)
fragmentManager.beginTransaction().attach(this).commitAllowingStateLoss()
}

Handling orientation changes with Fragments

I'm currently testing my app with a multipane Fragment-ised view using the HC compatibility package, and having a lot of difficultly handling orientation changes.
My Host activity has 2 panes in landscape (menuFrame and contentFrame), and only menuFrame in portrait, to which appropriate fragments are loaded. If I have something in both panes, but then change the orientation to portrait I get a NPE as it tries to load views in the fragment which would be in the (non-existent) contentFrame. Using the setRetainState() method in the content fragment didn't work. How can I sort this out to prevent the system loading a fragment that won't be shown?
Many thanks!
It seems that the onCreateViewMethod was causing issues; it must return null if the container is null:
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
if (container == null) // must put this in
return null;
return inflater.inflate(R.layout.<layout>, container, false);
}
Probably not the ideal answer but if you have contentFrame for portrait and in your activity only load up the menuFrame when the savedInstanceState is null then your content frame fragments will be shown on an orientation change.
Not ideal though as then if you hit the back button (as many times as necessary) then you'll never see the menu fragment as it wasn't loaded into contentFrame.
It is a shame that the FragmentLayout API demos doesn't preserve the right fragment state across an orientation change. Regardless, having thought about this problem a fair bit, and tried out various things, I'm not sure that there is a straightforward answer. The best answer that I have come up with so far (not tested) is to have the same layout in portrait and landscape but hide the menuFrame when there is something in the detailsFrame. Similarly show it, and hide frameLayout when the latter is empty.
Create new Instance only for First Time.
This does the trick:
Create a new Instance of Fragment when the
activity start for the first time else reuse the old fragment.
How can you do this?
FragmentManager is the key
Here is the code snippet:
if(savedInstanceState==null) {
userFragment = UserNameFragment.newInstance();
fragmentManager.beginTransaction().add(R.id.profile, userFragment, "TAG").commit();
}
else {
userFragment = fragmentManager.findFragmentByTag("TAG");
}
Save data on the fragment side
If your fragment has EditText, TextViews or any other class variables
which you want to save while orientation change. Save it
onSaveInstanceState() and Retrieve them in onCreateView() method
Here is the code snippet:
// Saving State
#Override
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
outState.putString("USER_NAME", username.getText().toString());
outState.putString("PASSWORD", password.getText().toString());
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup parent, Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.user_name_fragment, parent, false);
username = (EditText) view.findViewById(R.id.username);
password = (EditText) view.findViewById(R.id.password);
// Retriving value
if (savedInstanceState != null) {
username.setText(savedInstanceState.getString("USER_NAME"));
password.setText(savedInstanceState.getString("PASSWORD"));
}
return view;
}
You can see the full working code HERE

Categories

Resources