In the following code, the fragments are being dynamically inflated from a layout xml. The issue i am facing is that I dont see the fragments when i rotate the device.
If I look at the fragmentManager, I do see fragments are there. They are added and attached, but no where on the screen.
MenuCard extends Activity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.content);
leftContainer = (FrameLayout) findViewById(R.id.leftContainer);
rightContainer = (FrameLayout) findViewById(R.id.rightContainer);
Fragment fragment = mFragmentManager.findFragmentById(R.id.leftFragment);
if (fragment == null) {
LayoutInflater.from(this).inflate(R.layout.left_fragment, leftContainer);
}
fragment = mFragmentManager.findFragmentById(R.id.rightFragment);
if (fragment == null) {
LayoutInflater.from(this).inflate(R.layout.right_fragment, rightContainer);
}
}
}
}
}
R.layout.left_fragment
<?xml version="1.0" encoding="utf-8"?>
<fragment xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="#+id/leftFragment"
android:name="com.example.LeftFragment"
tools:layout="#layout/left_fragment">
</fragment>
R.layout.right_fragment
<?xml version="1.0" encoding="utf-8"?>
<fragment xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="#+id/rightFragment"
android:name="com.example.LeftFragment"
tools:layout="#layout/left_fragment">
</fragment>
Adding the code for R.layout.content
<?xml version="1.0" encoding="utf-8"?>
<android.support.v4.widget.SlidingPaneLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/spl"
android:layout_width="match_parent"
android:layout_height="match_parent">
<FrameLayout
android:id="#+id/leftContainer"
android:layout_width="320dp"
android:layout_height="match_parent"
android:background="#00AFF0"
/>
<FrameLayout
android:id="#+id/rightContainer"
android:layout_width="match_parent"
android:layout_height="match_parent"
/>
</android.support.v4.widget.SlidingPaneLayout>
you can try to add following lines in your manifest inside fragment Activity's declaration:
android:configChanges="screenSize|orientation"
This will prevent onCreate on screen orientation change.For handling of orientation change, you can override onConfigurationChanges.
So after a lot of reading of the fragment manager code i came across these magical lines:
// For fragments that are created from a layout, when restoring from
// state we don't want to allow them to be created until they are
// being reloaded from the layout.
which means the correct code for my case is
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.content);
leftContainer = (FrameLayout) findViewById(R.id.leftContainer);
rightContainer = (FrameLayout) findViewById(R.id.rightContainer);
LayoutInflater.from(this).inflate(R.layout.left_fragment, leftContainer);
LayoutInflater.from(this).inflate(R.layout.right_fragment, rightContainer);
}
}
SupportFragmentManager will reuse the fragments in case of orientation change and only inflate the layouts.
I never tried your design but I find it interesting. Although I don't recommend it.
The UI is not recreated because orientation is changed. This is because Activity is not recreated, hence onCreate is not triggered when orientation is changed. When the screen is changed due to different factors, onCreateView is triggered. This I never tried in the Activity subclass. It is normally done in a Fragment subclass. Actually, onCreate is only triggered when the App is stopped due to user action or a crash.
If/when I find a good sample of onCreateView method in Activity, I'll post it.
Related
I've made two XML files one for portrait mode and the other is for landscape
When i start the app it runs normally and after flipping the screen for the second time it crashes and gives this error:java.lang.IllegalStateException: Fragment has not been attached yet.
Eventhough the landscape xml file contains static fragments and the portriat one contains nothing(in order to add the fragments dinamically on further steps).Here is my XML File:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<fragment
android:name="com.example.alihaidar.fragmentlab.fragList"
android:layout_width="0dp"
android:layout_weight="1"
android:layout_height="match_parent"
android:id="#+id/frag_list"
/>
<fragment
android:name="com.example.alihaidar.fragmentlab.fragContent"
android:layout_width="0dp"
android:layout_weight="2"
android:layout_height="match_parent"
android:id="#+id/frag_content"
/>
</LinearLayout>
and this is mainActivity it contains nothing :
public class MainActivity extends AppCompatActivity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
}
You need to make a validation to check if the Fragment is attached to its parent using isAdded() method:
if (!isAdded()){
return; //Not attached
}else{
//Attached!
}
isAdded() Return true if the fragment is currently added to its
activity.
Another option, define in your activity the property:
<activity
android:configChanges="orientation|keyboardHidden|screenSize">
to avoid the destruction of the parent activity when you rotate the device.
I have a two variants of activity_main.xml (for portrait/landscape orientation). In this activity, user can choose items and browse detailed information about selected item.
In portraint orientation, fragments added to flFragmentContainer dynamically, after item choosing, details fragment replaces list fragment:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<FrameLayout
android:id="#+id/flFragmentContainer"
android:layout_width="match_parent"
android:layout_height="match_parent"></FrameLayout>
</LinearLayout>
In landscape orientation, fragments described in XML file statically:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:orientation="vertical">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="horizontal">
<fragment
android:id="#+id/frgTaskList"
android:name="com.exprod.xchecklist.fragments.TaskListFragment"
android:layout_width="0dp"
android:layout_weight="1"
android:layout_height="match_parent"
tools:layout="#layout/fragment_task_list" />
<fragment
android:id="#+id/frgTaskDetails"
android:name="com.exprod.xchecklist.fragments.TaskDetailsFragment"
android:layout_width="0dp"
android:layout_weight="1"
android:layout_height="match_parent"
tools:layout="#layout/fragment_task_details">
</fragment>
</LinearLayout>
</LinearLayout>
Here is the code of onCreate() method (of MainActivity).
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
FragmentManager fragmentManager = getSupportFragmentManager();
TaskDetailsFragment taskDetailsFragment = (TaskDetailsFragment)fragmentManager.findFragmentById(R.id.frgTaskDetails);
isDynamicMode = taskDetailsFragment == null || !taskDetailsFragment.isInLayout();
if(isDynamicMode){
FragmentTransaction transaction = fragmentManager.beginTransaction();
Fragment taskListFragment = new TaskListFragment();
if (savedInstanceState == null) {
transaction.add(R.id.flFragmentContainer, taskListFragment, TaskListFragment.FRAGMENT_TAG);
}else{
transaction.replace(R.id.flFragmentContainer, taskListFragment, TaskListFragment.FRAGMENT_TAG);
}
transaction.commit();
}
}
PROBLEM: When I rotate device to landscape orientation, I get twice calls of creation methods in first fragment (TaskListFragment) (onCreateView(), onActivityCreated(), ... ). This indicates that the old fragment remains in activity and recreated on orientation change.
How I can finally destroy old fragment?? I did not find the answer on the Internet.
P.S: Sorry for my bad English...
I see at least two approaches to do what you want.
Handle orientation change yourself:
As described in this document, you can prevent automatic Activity destroy and re-creation and handle orientation change yourself. In this case, you would remove the old Fragment before inflating a new layout into Activity.
Remove the old Fragment after Activity re-creation:
You could also add some logic that determines the orientation as described here (or use your current heuristic that sets isDynamicMode), and manually remove the unnecessary Fragments during onCreate().
The better way:
Although it is possible to do what you want, I can't undertand why would you like to do it this way - you use TaskListFragment in both portrait and landscape configurations, therefore the most logical thing to do is to reuse it. Except for simplifying things, it will also let you keep the state of that Fragment on orientation change (which, I believe, is desirable in your case).
Thanks for Vasiliy answer. I follow this link
and add
android:configChanges="oritentation"
to my manifest file:
<activity android:name=".MyActivity"
android:configChanges="orientation"
android:label="#string/app_name">
and the annoying behavior (fragment not attached to activity) when screen is rotated disappears
Just add to your activity
#Override
public void onSaveInstanceState(Bundle outState) {}
because implementation of this method in Android's Activity that is a parent of all Activities, saved Fragment.
I am using an EditText under the Material Design and I am having problems in screen orientation. I was able to save the content onSavedInstanceState and reinstate back what was written on edittext back again but there seems to be a bug-like behavior going on.
Look at these following pics:
Original orientation when activity start:
When I rotate the screen, I still get a hold of the content but when I place the carret on an existing text span, it returns the carret to 0 (Beginning of edit text) and then when I typed something it looks like this:
I encountered this situation once and that was very long time ago. IIRC I was able to solve it by completely replacing the content onCreateView's savedInstanceState, it doesn't seem to work anymore.
Any ideas?
[EDIT]
A lot of people will be expecting to answer "ONCONFIGURATIONCHANGED". No please, this is not merely the solution for the underlying problem. In fact, I tried it and didn't work, don't want to implement that solution anyways. I believe this doesn't warrant band aid solution, at least as a last resort.
Read the answer to this: Why not use always android:configChanges=“keyboardHidden|orientation”?
Here is my layout file for the 'Fragment' containing the edit text:
<?xml version="1.0" encoding="utf-8"?>
<android.support.design.widget.CoordinatorLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="true"
android:orientation="vertical"
tools:context=".HomeScreenActivity"
android:weightSum="1">
<android.support.design.widget.AppBarLayout
android:id="#+id/appbar_note"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:theme="#style/ThemeOverlay.AppCompat.Dark.ActionBar"
android:fitsSystemWindows="true">
<android.support.design.widget.CollapsingToolbarLayout
android:id="#+id/collapsingtoolbarlayout_note"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_scrollFlags="scroll|exitUntilCollapsed"
app:expandedTitleMarginStart="48dp"
app:expandedTitleMarginEnd="64dp">
<android.support.v7.widget.Toolbar
android:id="#+id/toolbar_note"
android:layout_width="match_parent"
android:layout_height="?attr/colorPrimary"
app:popupTheme="#style/ThemeOverlay.AppCompat.Light"
app:contentInsetLeft="0dp"
app:contentInsetStart="0dp"
app:layout_collapseMode="pin">
....
</android.support.v7.widget.Toolbar>
</android.support.design.widget.CollapsingToolbarLayout>
</android.support.design.widget.AppBarLayout>
<android.support.v4.widget.NestedScrollView
android:layout_width="match_parent"
android:layout_height="fill_parent"
android:fillViewport="true"
app:layout_behavior="#string/appbar_scrolling_view_behavior">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<EditText
android:id="#+id/edittext_note"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="6"
android:gravity="top"
android:freezesText="true"
android:layout_marginLeft="16dp"
android:layout_marginStart="16dp"
android:layout_marginEnd="16dp"
android:layout_marginRight="16dp"
android:layout_marginTop="16dp" />
<include
layout="#layout/layout_rte_toolbar_black_icon"
android:layout_width="match_parent"
android:layout_height="0dp"
android:gravity="bottom"
android:layout_weight="0.60"/>
</LinearLayout>
</android.support.v4.widget.NestedScrollView>
</android.support.design.widget.CoordinatorLayout>
Next here is my Activity layout, a very simple layout actually:
<FrameLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="#+id/framelayout_note"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
Here is create method callback for Activity:
#Override
protected void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
Log.i("NoteActivity.OnCreate" , TAG);
setContentView(R.layout.activity_note);
NoteFragment fragment = NoteFragment.createInstance();
fragment.setOnToolbarButtonSelected(this);
getSupportFragmentManager()
.beginTransaction()
.add(R.id.framelayout_note , fragment)
.commit();
}
Here is the create/onSavedInstanceState method callback for the fragment
#Override
public void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
Log.i("NoteFragment.onCreateView", TAG);
if(savedInstanceState != null)
{
mTitle = savedInstanceState.getString(KEY_EDITTEXT_TITLE, TAG);
mContent = Html.fromHtml(savedInstanceState.getString(KEY_EDITTEXT_CONTENT, TAG));
}
mNoteDataController = ((NotifireApplication) getActivity().getApplication()).getNoteDataController();
setHasOptionsMenu(true);
setRetainInstance(true);
}
#Override
public View onCreateView(LayoutInflater inflater , ViewGroup parent, Bundle savedInstanceState)
{
Log.i("NoteFragment.onCreateView", TAG);
View view = inflater.inflate(R.layout.fragment_note, parent, false);
mNoteContent = (EditText) view.findViewById(R.id.edittext_note);
mNoteTitle = (EditText) view.findViewById(R.id.edittext_note_title);
if(mContent != null) mNoteContent.setText(mContent);
if(mTitle != null) mNoteTitle.setText(mTitle);
initToolbar(view);
return view;
}
#Override
public void onSaveInstanceState(Bundle savedInstanceState)
{
Html.toHtml(mNoteContent.getText());
savedInstanceState.putString(KEY_EDITTEXT_CONTENT, Html.toHtml(mNoteContent.getText()));
savedInstanceState.putString(KEY_EDITTEXT_TITLE, mNoteTitle.getText().toString());
super.onSaveInstanceState(savedInstanceState);
}
Got the problem. Actually All this work that you have done in fragment should have been done in activity. In this scenario since you are using frame layout, I think what is happening is that your old fragment is in place and once you rotate the device a new instance of fragment gets created and placed above it.
In my opinion this is the reason why your older text is there which is actually in your previous fragment. My opinion is either handle onConfiguration change and don't create new fragment on orientation change.
Second if you want to test what I'm saying is you can use linearlayout with vertical orientation and you will see that there are two fragments.
When the screen orientation changes, two instances of NoteFragment are being created and added to the container view. This is your root problem. One NoteFragment is being created and added by FragmentActivity#onCreate. Another NoteFragment is being created in the onCreate callback of your activity which subclasses FragmentActivity.
You can fix this by checking whether savedInstanceState is null before adding your fragment in onCreate. If savedInstanceState is null, then add your fragment. If not, then the activity is being re-created after a configuration change and the logic of FragmentActivity will restore fragments fro you, so do nothing.
In the onConfigurationChanged() First Get the data of your Edit text into a global variable and then call setContentView() and now set the Data into your Edit text.
This should work.
I want to design a dynamic UI for my application so in screens with more than 500dp width it will show 2 fragment and otherwise it shows a single fragment.
Here is my layout files:
layout/activity_main:
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/frag_container"
android:layout_width="match_parent"
android:layout_height="match_parent" >
</FrameLayout>
layout-w500dp/main_activity:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="match_parent">
<fragment android:name="ir.focusmag.focus.PostsFragment"
android:id="#+id/posts_frag_container"
android:layout_weight="2"
android:layout_width="0dp"
android:layout_height="match_parent"
tools:layout="#layout/fragment_posts" />
<fragment android:name="ir.focusmag.focus.PhotosFragment"
android:id="#+id/photos_frag_container"
android:layout_weight="2"
android:layout_width="0dp"
android:layout_height="match_parent"
tools:layout="#layout/fragment_photos" />
</LinearLayout>
in MainActivity.java there is a check to see whether frag_container is present or not and if it's there it will load the PostsFragment into it:
public class MainActivity extends ActionBarActivity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// Check whether the activity is using the layout version with
// the fragment_container FrameLayout. If so, we must add the first fragment
if (findViewById(R.id.frag_container) != null) {
// However, if we're being restored from a previous state,
// then we don't need to do anything and should return or else
// we could end up with overlapping fragments.
if (savedInstanceState != null) {
return;
}
firstFragment = new PostsFragment();
firstFragment.setArguments(getIntent().getExtras());
// Add the fragment to the 'fragm_container' FrameLayout
getSupportFragmentManager().beginTransaction()
.add(R.id.frag_container, firstFragment).commit();
}
}
And there is a Listener for clicking on a post to replace the fragment with the details fragment.
The important point here is if we're being restored from a previous state it doesn't do anything.
In usual scenarios this method works but if the user stars the application in landscape mode ( screen width > 500dp ) MainActivity checks for frag_container and as it's not there It will do nothing and then the user will rotates the device to portrait mode and as the savedInstanceState is not null anymore nothing will be loaded into frag_container.
If I remove the check for savedInstanceState, whenever the user rotates the device PostsFragment will be loaded and it's not wanted. for example the user is checking the details and rotates the device and it shows the posts instead of the details.
What can I do to stop this kind of behavior?
I'm following Android Developer's tutorial to do this.
Thanks in advance
This approach is a little bit outdated. If you just started playing with Android and want to see how to handle things modern way, watch Developing Android Apps video course from Google by Google. They will show you how to deal with Master/Detail pattern properly. If you would like to jump immediately to the code, start your Android Studio and create new Activity with the wizard and pick Master/Detail form.
Your new activity's onCreate() method would look like this:
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_item_list);
if (findViewById(R.id.item_detail_container) != null) {
// The detail container view will be present only in the
// large-screen layouts (res/values-large and
// res/values-sw600dp). If this view is present, then the
// activity should be in two-pane mode.
mTwoPane = true;
// In two-pane mode, list items should be given the
// 'activated' state when touched.
((ItemListFragment) getFragmentManager()
.findFragmentById(R.id.item_list))
.setActivateOnItemClick(true);
}
// TODO: If exposing deep links into your app, handle intents here.
}
Comparing to your current approach, this time for single pane mode fragment is added within XML layout so you don't have to bother with unexpected behavior. You will also see there how layout aliases works.
I know it's not a simple answer you may expect buy it may be the last moment for your application to apply this change. I really encourage you to do it.
If this change is not an option for you consider modifying single pane layout and include fragment there instead adding dynamically. Probably, at the moment, your are not benefit from dynamic fragment at all anyway.
I have an Activity and i had set activity's content view as "R.layout.main.xml".And i have an another class which contains animation created using openGL. Now i need to use this animation in the background of the Activity.
The code is like this
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main_pixie);
mGLView = new ClearGLSurfaceView(this);
setContentView(mGLView);
}
But my app is Crashing ..How can i solve this.
When you call the setContentView() a second time, you replace what had been set the first time, leaving you with only the background. The crash is most likely because you depend on the elements in the main layout, which is removed.
Rather than calling setContentView() twice, you should include the GLSurfaceView in the main layout. Below is an example of how this can be done:
<?xml version="1.0" encoding="UTF-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent>
<your.application.package.ClearGLSurfaceView
android:layout_width="match_parent"
android:layout_width="match_parent"/>
<!--put the rest of your layout here, i.e the contents of the original R.layout.main_pixie-->
</FrameLayout>
Then you can load this layout in your onCreate() as usual (main_pixie_new refers to the above xml, I just gave it that name to keep things as clear as possible):
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main_pixie_new);
}