fragments and onConfigurationChanged - android

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.

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;
}

Android fragment order after orientation changed

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"/>

How can I prevent a Fragment's onCreateView() from being called?

So here is my code. 'currentFragment' is simply a field that tracks what is currently being displayed. This is in a class that itself is a Fragment (so I have a fragment showing a fragment).
private void selectNavBarItem(NavbarItem v)
{
Fragment fragmentToUse = null;
if (v == setpointsNavItem)
{
fragmentToUse = setpointsFragment;
}
else if (v == rapidSetupNavItem)
{
fragmentToUse = rapidSetupFragment;
}
else if (v == outdoorResetNavItem)
{
fragmentToUse = outdoorResetFragment;
}
else if (v == rampDelayNavItem)
{
fragmentToUse = rampDelayFragment;
}
if (fragmentToUse != null)
{
FragmentManager fm = getFragmentManager();
FragmentTransaction ft = fm.beginTransaction();
if (currentFragment != null)
{
ft.detach(currentFragment);
}
currentFragment = fragmentToUse;
if (currentFragment.isDetached())
{
ft.attach(currentFragment);
}
else
{
ft.add(R.id.setup_content_holder, currentFragment);
}
ft.addToBackStack(null);
ft.commit();
}
Everything looks great, but the views are getting recreated for all the fragments (onCreateView() and onViewCreated()). I was hoping that attaching and detaching would work, but it doesn't. The reason I want to maintain the view is so the user's selections are still there when they navigate back.
Another option is showing and hiding, but I don't know how to make that work because the fragment that owns this code has a FrameLayout (R.id.setup_content_holder) that holds the fragment I want to add, and I can't just add four fragments to it but hide three of them. There is an option to add a fragment with no container, but I have no idea how that is supposed to work.
So, any ideas?
Try this, this will solve your frgment view r-creating issue;
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
if (mFragmentView != null) {
((ViewGroup) mFragmentView.getParent()).removeView(mFragmentView);
return mFragmentView;
}
mFragmentView = inflater.inflate(R.layout.home_fragment, container, false);
..... // your remaining code
}
The OnCreateView methods are always called within a Fragment.
To solve the problem you're describing what you really need to do is save the state of the fragment, then when it returns the application will restore what you saved.
e.g. (within the fragment class in question):
#Override
public void onSaveInstanceState(Bundle savedInstanceState) {
super.onSaveInstanceState(savedInstanceState);
savedInstanceStatePutString("userString", someTextView.getText().toString());
savedInstanceStatePutInt("userInt", userInt);
// etc...
}
#Override
public void onRestoreInstanceState(Bundle savedInstanceState) {
super.onRestoreInstanceState(savedInstanceState);
someTextView.setText(savedInstanceState.getString("userString"));
userInt = savedInstanceState.getInt("userInt");
}
That should solve your problem while hopping between fragments; the application will call onSaveInstanceState and onRestoreInstanceState when a fragment gets pushed into or pulled out of the stack.
It will not save them forever, however! If you need more persistent storage, look into other options such as saving to a sqlite database.
OP here.
So I hacked something together here, and I'm wondering if it's a good idea. I have a Fragment holding a View. The View contains everything I want to save (in the short term, of course - this isn't supposed to be any more persistent than RAM). When the Fragment calls onCreateView() I simply return the already-created View.
Now, I ran into an issue where the View was not being removed by the fragment manager. I added a call in onPause() to make sure it's removed from the parent.
Everything seems to work fine, but I want to make sure I'm not doing something really bad. I know Android really really wants to manage its view lifecycles itself, but I do not want it recreating them every damn time. They are complicated and I don't want to deal with re-initializing all the subview text/image/state. Will I run into issues in my attempt to do a run-around Android's normal operating procedure?
EDIT: forgot the code:
public class OutdoorResetFragment extends Fragment
{
private OutdoorResetView view;
public OutdoorResetFragment()
{
}
public void onAttach(Activity activity)
{
if (view == null || view.getContext() != activity)
{
view = new OutdoorResetView(activity);
}
super.onAttach(activity);
}
public void onPause()
{
super.onPause();
ViewGroup container = (ViewGroup) view.getParent();
if (container != null)
{
container.removeAllViews();
}
}
#Override
public View onCreateView(final LayoutInflater inflater, final ViewGroup container,
final Bundle savedInstanceState)
{
return view;
}
public OutdoorResetView getView()
{
return view;
}
}

Set new layout in fragment

I'm trying to change the layout of a fragment during runtime under a particular condition.
The initial layout in inflated within the onCreateView():
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
return inflater.inflate(R.layout.cancel_video, null);
}
Then sometime later within the fragment code I would like to replace the initial layout with some other layout.
I've tried a few things so far; this is the latest that I have:
private void Something(){
if(checkLicenseStatus(licenseStatus, statusMessage)){
View vv = View.inflate(getActivity(), R.layout.play_video, null);
//more code
}
}
How can I accomplish this?
You cannot replace the fragment's layout once it is inflated. If you need conditional layouts, then you either have to redesign your layout and break it down into even smaller elemens like Fragments. Alternatively you can group all the layout elements into sub containers (like LinearLayout), then wrap them all in RelativeLayout, position them so they overlay each other and then toggle the visibility of these LinearLayouts with setVisibility() when and as needed.
Yes, I have done this in following way. When I need to set a new layout(xml), the following code snippet should be executed.
private View mainView;
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup containerObject, Bundle savedInstanceState){
super.onCreateView(inflater, containerObject, savedInstanceState);
mainView = inflater.inflate(R.layout.mylayout, null);
return mainView;
}
private void setViewLayout(int id){
LayoutInflater inflater = (LayoutInflater) getActivity().getSystemService(Context.LAYOUT_INFLATER_SERVICE);
mainView = inflater.inflate(id, null);
ViewGroup rootView = (ViewGroup) getView();
rootView.removeAllViews();
rootView.addView(mainView);
}
Whenever I need to change the layout I just call the following method
setViewLayout(R.id.new_layout);
Use a FragmentTransaction through the FragmentManger
FragmentManager fm = getFragmentManager();
if (fm != null) {
// Perform the FragmentTransaction to load in the list tab content.
// Using FragmentTransaction#replace will destroy any Fragments
// currently inside R.id.fragment_content and add the new Fragment
// in its place.
FragmentTransaction ft = fm.beginTransaction();
ft.replace(R.id.fragment_content, new YourFragment());
ft.commit();
}
The code for the class YourFragment is just a LayoutInflater so that it returns a view
public class YourFragment extends Fragment {
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.your_fragment, container, false);
return view;
}
}
I will change whole layout. You can change only a specific portion of your layout.
Add a root FrameLayout in your_layout.xml, it contains nothing.
<FrameLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/fl_content"
android:layout_width="match_parent"
android:layout_height="match_parent" />
Set your content in the java code.
ViewGroup flContent = findViewById(R.id.fl_content);
private void setLayout(int layoutId) {
flContent.removeAllViews();
View view = getLayoutInflater().inflate(layoutId, flContent, false);
flContent.addView(view);
}
You can change layoutId free at some point.
If you have some listeners, you must set again.

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()
}

Categories

Resources