Android. Fragment stays in activity after screen rotation issue - android

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.

Related

android:fragments crash on screen Orientation

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.

Fragments not visible on orientation change

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.

How to reload fragments into the backstack after orientation change

My android app has a registration flow setup with an Activity and I load the steps as fragments into the activity layout.
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:orientation="horizontal"
tools:context="com.example.android.ui.RegisterActivity">
<LinearLayout
android:id="#+id/register_container"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent"></LinearLayout>
</RelativeLayout>
Now each fragment class implements a public interface implemented in the RegisterActivity so that I know to load the next step fragment and I add the new fragment to the backstack
mFragmentTransaction.addToBackStack(mStepOne.TAG);
now it all works fine all the way through 4 steps and I can navigate back through the steps while retaining the inputted data in each fragment IF it stays in the same orientation (portait)
BUT once i change the orientation, the fragment views disappear
if I am up to step 3, I can still hit the back button and it will go back showing me the fragment that is meant to be there by viewing the backstack changed listener
mFragmentManager.addOnBackStackChangedListener(new FragmentManager.OnBackStackChangedListener() {
#Override
public void onBackStackChanged() {
if(mFragmentManager.getBackStackEntryCount() > 0) {
Log.d("BACK STACK", "TAG (name): " + mFragmentManager.getBackStackEntryAt( (mFragmentManager.getBackStackEntryCount() - 1)).getName());
}
}
});
The count and tags of the fragments is retained but not the views. The RegisterActivity onCreateView loads the terms fragment into the view and it is what remains in view when navigating back through the steps.
mFragmentManager = getSupportFragmentManager();
mFragmentTransaction = mFragmentManager.beginTransaction();
mTerms = new RegisterFragmentTerms();
mFragmentTransaction.add(R.id.register_container, mTerms);
mFragmentTransaction.commit();
It seems to me the views are loaded into the RegisterActivity on top of each other and cleared on orientation change. Is it possible to solve what I'm trying to do? or have I implemented it wrong? It's my first android app :)
Cheers
It seems to be caused by Activity recreation. Have you ever add android:configChanges="orientation|keyboardHidden|screenSize" in your AndroidManifest.xml to your Activity? , if you add that flag to you activity, it will not be destroyed when orientation changes and the fragments stack will not disappear.

Building a Dynamic UI with Fragments

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.

Implementing Fragments Androids

I am implementing fragments for the first time so please help me.
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="match_parent">
<fragment android:name="com.example.news.ArticleListFragment"
android:id="#+id/list"
android:layout_weight="1"
android:layout_width="0dp"
android:layout_height="match_parent" />
<fragment android:name="com.example.news.ArticleReaderFragment"
android:id="#+id/viewer"
android:layout_weight="2"
android:layout_width="0dp"
android:layout_height="match_parent" />
</LinearLayout>
I want that fragment having the id as 'list' should remain constant but the the fragment having id 'viewer' should be able to call different classes.
(Note that the classes extend Activity.)
My question is simple: I have four classes(Extending ACTIVITY). I want to divide the screen into two parts. The left Side remains constant which contains the listview. On list view's click I want to open my Class(Extending ACTIVITY), but only in the right portion(remaining screen).
It is a basic question. You should start from here. And this topic can help you also.
Fragments are like seperate acticities, so unless u make the changes the action on one fragment will not affect the other fragments.
Assuming u have a listview on the left fragment, in its activity place a onItemClickListener.
For each itemclick switch the activity on the right fragment.
Sample Code for the OnItemClick Event
Fragment fragment=new activity1();
fragmentManager fm=getFragmentManager();
FragmentTransaction ft=fm.beginTransaction();
ft.replace(R.id.frame2,fragment);
ft.commit();
In the above code segment activity1 is the new class want to attach to the right fragment. R.id.frame2 is the id of the framelayout that is used with the right fragment.
According to the android documentation of fragments:
A Fragment represents a behavior or a portion of user interface in an Activity. You can combine multiple fragments in a single activity to build a multi-pane UI and reuse a fragment in multiple activities. You can think of a fragment as a modular section of an activity, which has its own lifecycle, receives its own input events, and which you can add or remove while the activity is running (sort of like a "sub activity" that you can reuse in different activities).
http://developer.android.com/guide/components/fragments.html
what i understood from your question is that you want to the content of your fragement viewer at run-time. a possible solution which i can suggest for this is:
Instead of your four classes extending Activity, extend Fragment, each having its own layout. Modify the main layout file to look something like this:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="match_parent">
<fragment android:name="com.example.news.ArticleListFragment"
android:id="#+id/list"
android:layout_weight="1"
android:layout_width="0dp"
android:layout_height="match_parent" />
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/viewer"
android:layout_weight="2"
android:layout_width="0dp"
android:layout_height="match_parent" >
</FrameLayout>
</LinearLayout>
the FrameLayout will basically act as a container for your fragments, which you can dynamically load at run-time(by clicking the ListView). This tutorial will help you out with it:
http://developer.android.com/training/basics/fragments/fragment-ui.html
Hope my answer helps you in some way.
The Fragment class can be used many ways to achieve a wide variety of results. In its core, it represents a particular operation or interface that is running within a larger Activity. A Fragment is closely tied to the Activity it is in, and can not be used apart from one. Though Fragment defines its own lifecycle, that lifecycle is dependent on its activity: if the activity is stopped, no fragments inside of it can be started; when the activity is destroyed, all fragments will be destroyed.
MyFragment newFragment = new MyFragment();// MyFragment is a Fragment class
FragmentTransaction transaction = getFragmentManager().beginTransaction();
transaction.add(R.id.fra,newFragment, tag);
transaction.addToBackStack(null);
transaction.commit();
sample code
in sample code change
ft.add(android.R.id.content,fragTwo, "tag");
to
ft.add(R.id.fra,fragTwo, "tag");
and add some code in detail.java
public void onStart() {
// TODO Auto-generated method stub
tv.setText(data);
b.setOnClickListener(new OnClickListener() {
public void onClick(View v) {
// TODO Auto-generated method stub
FragmentManager fm = getFragmentManager();
FragmentTransaction ft = fm.beginTransaction();
fm.beginTransaction();
Fragment fragTwo = new MyFragment();
//String tag = getActivity().GetFragmentID();
Fragment f= fm.findFragmentById(getId());
ft.replace(R.id.fra,fragTwo, "tag");
ft.hide(f);
ft.commit();
}
});
super.onStart();
}

Categories

Resources