I am using webview to create a basic web browser,
Currently I have one activity and multiple fragments :
-- Browser
-- something else
-- Something else
in Browser Fragment I have the webview :
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
app:layout_behavior="#string/appbar_scrolling_view_behavior"
tools:context=".fragments.BrowserFragment">
<android.support.v4.widget.SwipeRefreshLayout
android:id="#+id/swipe_refresh"
android:layout_width="match_parent"
android:layout_height="match_parent">
<RelativeLayout
android:id="#+id/webview_holder"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:clickable="true">
<include
android:id="#+id/button_layout"
layout="#layout/bottom_btn_layout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"/>
<LinearLayout
android:id="#+id/web_page_container"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_above="#id/button_layout"
android:orientation="vertical">
</LinearLayout>
<WebView
android:id="#+id/web_page"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_above="#id/button_layout"/>
</RelativeLayout>
</android.support.v4.widget.SwipeRefreshLayout>
</LinearLayout>
I am able to save the state of the webview on screen rotation , by using
#Override
public void onSaveInstanceState(Bundle outState) {
if(mWebView != null){
mWebView.saveState(outState);
}
super.onSaveInstanceState(outState);
}
and calling this OnActivityCreated :
mWebView = (WebView)getActivity().findViewById(R.id.web_page);
if (savedInstanceState != null){
mWebView.restoreState(savedInstanceState);
}else{
mWebView.loadUrl(makeUrl(url));
}
This works fine when screen rotates and activity is recreated.
But when I use navigation drawer and replace fragment in the same container and then try to come back to same fragment . The state of webview is lost.
I get the point that onSaveInstanceState() is not called when there is Fragment transaction. So I tried saving the webView state by calling onSaveInstanceState() from OnPause of the fragment. But in that case I get savedInstanceState as null in OnCreateView so I am not able to restore state.
I need to maintain state of multiple webviews and comeBack to them on click of a list.
I also tried saving webview, when onSave() is called in fragment, in a static HashMap in activity and try to assign it to webview of newFragment. But I get Blank screen in that case.
Could someone give me pointers how I can approach this.
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.
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.
I have an activity of type FragmentActivity. Inside I have some content using view pager, where each scrollable tab is a fragment:
<android.support.v4.view.ViewPager
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="#+id/pager"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".OpenStack" >
<!--
This title strip will display the currently visible page title, as well as the page
titles for adjacent pages.
-->
<android.support.v4.view.PagerTitleStrip
android:id="#+id/pager_title_strip"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="top"
android:background="#33b5e5"
android:paddingBottom="4dp"
android:paddingTop="4dp"
android:textColor="#fff" />
</android.support.v4.view.ViewPager>
Inside the fragment I'm opening a new activity when user taps on the item in the list like this:
Intent myIntent = new Intent(getActivity(), InstanceActivity.class);
getActivity().startActivity(myIntent);
But when i click back in the InstaceActivity to return to the one with fragments it is destroyed and created again. I have to no idea why that happens. Should I do something special to keep the FragmentActivity in the backstack?
It doesn't even help to save the state with:
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
outState.putString("CONNTOKEN", isConnToken);
outState.putString("NOVAURL", isNovaURL);
}
Because in the oncreate event of the FragmentActivity the savedInstanceState is NULL when I press back ??
Try this: addToBackStack. ;)