Fragment not calling onDestroyView while parent layout removed - java

I have been stuck with a very strange problem with layouts & fragments.
I have been assigned with fixing an app that has really poor logic & design. It has single activity & all layout changes are done by simply removing content on wrapper (LinearLayout) and adding views to it.
I have been trying to inflate layout with < fragment > inside. And it works just fine. But when I change wrapper content (removeAllViews()) with different content and then try to reinflate layout with fragment again I get exception with:
12-18 18:28:36.711: E/AndroidRuntime(11192): Caused by: java.lang.IllegalArgumentException: Binary XML file line #7: Duplicate id 0x7f07014d, tag null, or parent id 0x0 with another fragment for com.test.fragments.SearchTabhostFragment
While I remove parent layout (wrapper) - Fragment is not executing any of the lifecycle functions (onPause, onStop, onDestroyView etc).
Inflating & reinflating later on:
LayoutInflater inflater = (LayoutInflater) context
.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
view = inflater.inflate(R.layout.search, wrapper);
Destroying:
if (wrapper.getChildCount() > 0) {
wrapper.startAnimation(out);
wrapper.removeAllViews();
}
Search XML:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical" >
<fragment
android:id="#+id/fragment1"
android:name="com.test.fragments.SearchTabhostFragment"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentTop="true" />
</RelativeLayout>
Any idea what I might do to clear it from FragmentManager? Is there an event I might use on layout to find out it is being removed?

Related

XML layout doesn't appear in R.layout

I am using the androidX CardView.
In order to make a recyclerview adapter for the cards I need to inflate the custom card layout I have made.
Unfortunately the xml of the custom card view doesn't appear in R.layout.(xml_name),while i find normally all the other layouts.
This the code inside the adapter class where I need to inflate:
public MyViewHolder onCreateViewHolder(#NonNull ViewGroup parent, int viewType) {
View view;
LayoutInflater mInflater = LayoutInflater.from(mContext);
view = mInflater.inflate(R.layout.cardview_item_answer); // not finding this layout
return null;
}
This is the card layout xml (cardview_item_answer.xml) :
<?xml version="1.0" encoding="utf-8"?>
<androidx.cardview.widget.CardView xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="160dp"
android:layout_height="160dp"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_margin="5dp"
app:cardCornerRadius="4dp"
>
<androidx.appcompat.widget.LinearLayoutCompat
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<androidx.appcompat.widget.AppCompatImageView
android:id="#+id/image_card"
android:layout_width="match_parent"
android:layout_height="100dp"
android:scaleType="centerCrop"
android:background="#2d2d2d2d">
</androidx.appcompat.widget.AppCompatImageView>
<androidx.appcompat.widget.AppCompatTextView
android:id="#+id/text_card"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:text="test"
android:textSize="13sp"
android:textColor="#color/black"
android:layout_gravity="center"
></androidx.appcompat.widget.AppCompatTextView>
</androidx.appcompat.widget.LinearLayoutCompat>
Do you suggest any ideas ?
Thank you very much !
Solved !
If you ever have this problem -> clean project and invalidate Caches/Restart.
If the XML contains any errors, your build will likely fail - this will cause R.layout to yield unexpected results. I am not seeing a closing tag for the CardView in cardview_item_answer.xml
Try adding a closing tag </androidx.cardview.widget.CardView> at the end of cardview_item_answer.xml
For some reasons neither of the solutions above worked for me.
I just had to use the full path to the layout before being able to use it. In my case the item of a spinner
So I did this:
import com.myProject.R.layout.spinner_item
It is never a suggested autocompletion, at least in my case, in model views. It is in the fragments. Can't make sense of that, but it works in view models too.

Replace a full screen fragment with another

I have a fragment (fragment_1) which gets displayed when the main activity starts and displays a full screen layout (layout_1). Now i have added a button, clicking on which will start a new fragment (fragment_2)and replace the entire layout (layout_1) with my new layout (layout_2).
My first fragment (fragment_1) has a ScrollView as my root view.
<ScrollView
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:background="#fff"
android:scrollbarSize="0dp"
android:id="#+id/scroll_main"
tools:context=".MainActivity">
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:fitsSystemWindows="true">
....
</RelativeLayout>
</ScrollView>
Now i want to start a new fragment (fragment_2) on a button click and display another layout (layout_2) but i don't know how to do that.
My main activity had a frame layout like this
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="#+id/root_container">
And i used to set this as my default layout using
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
After this i start my first fragment and display the layout_1.
Now from fragment_1 i want to start fragment_2 and display the layout_2. I have read online articles and documentation where i can't find and solution regarding this. Pleas Note :" I don't want to simply replace a part of my layout_1 and replace it with my layout_2 rather i want my layout_1 totally gets replaced with layout_2 on a button click"
Replacing fragments seems like a job for the fragment manager.
This answer might help you Replacing a fragment with another fragment inside activity group

Fragment Confusion

The Android developer documentation for fragments says this:
"Note: When you add a fragment to an activity layout by defining the
fragment in the layout XML file, you cannot remove the fragment at
runtime. If you plan to swap your fragments in and out during user
interaction, you must add the fragment to the activity when the
activity first starts, as shown in the next lesson."
And partly because of this I got into the habit of always using the fragment manager to add/remove fragments to/from my user interface (even if I did not want to "hot swap" them at runtime). I am 100% certain that when I tried to remove/replace "hard wired" XML fragments at runtime my app crashed with an exception.
I haven't really worked with XML fragments for months, but today, on a lark, I decided to play around and I find that I am able to swap XML fragments for other fragments and...it works? I can't find anything online that discusses a recent change in this behavior. It just works.
My layout code is here:
<?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:id="#+id/fl_frag"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingBottom="#dimen/activity_vertical_margin"
android:paddingLeft="#dimen/activity_horizontal_margin"
android:paddingRight="#dimen/activity_horizontal_margin"
android:paddingTop="#dimen/activity_vertical_margin"
android:orientation="vertical"
tools:context="mobappdev.demo.myapplication.MainActivity">
<fragment
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:id="#+id/frag"
android:name="mobappdev.demo.myapplication.BlankFragment"/>
<Button
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:text="#string/click_me"
android:onClick="clickMe"/>
</LinearLayout>
And here is the code that replaces the fragment:
public void clickMe(View view) {
FragmentManager manager = getSupportFragmentManager();
BlankFragment newFrag = BlankFragment.newInstance();
Fragment oldFrag = manager.findFragmentById(R.id.frag);
Log.i("TESTING", "old frag is null: " + (oldFrag == null));
manager.beginTransaction()
.replace(R.id.fl_frag, newFrag)
.commit();
}
It works without any problems. I've tried variations (putting the XML fragment in a FrameLayout for example) and it all seems to work just fine. I even tried variations such as remove/add and just remove and it all works without a problem.
So what am I missing?
Instead of replacing the fragment itself, you're adding a new fragment on top of that one. It's just a small issue with IDs, but you're replacing using the FragmentManager on the container (LinearLayout) instead of doing it on the fragment itself (R.id.fl_frag instead of R.id.frag)

Best practice with android fragments?

at the moment I'm calling and building fragments like this:
if (getSupportFragmentManager().findFragmentById(R.id.fragment_list) == null) {
list = new MyListFragment();
getSupportFragmentManager().beginTransaction().add(R.id.fragment_list, list).commit();
}
But I wonder if this is so called best practice, because this seems to me to be much boilerplate code. Are there better ways?
Use XML layouts and Fragment classes. Here I create a layout with 2 fragments. The class inflates the layout fragment_actionbarcompat.xml (that code isn't shown here but it's a basic layout file). And I create a layout file for the activity that houses the 2 fragments.
The ActionBarCompatFragment class overrides the onCreateView method to inflate it's layout. That gets injected into the fragment tag layoutwise.
In your case normally you don't just add in a plain ListFragment, you extend ListFragment and add your custom code into it. It's a way of better supporting fancy patterns like Model-View-Controller. Fragments are meant to be isolated compartments so you can reuse them between activities if you'd like. In most cases your class will hold the logic to load the data that the fragment needs.
ActionBarCompatFragment.java
#Override
public final View onCreateView(LayoutInflater inflater, ViewGroup root, Bundle savedInstanceState) {
final int layoutId = R.layout.fragment_actionbarcompat;
return inflater.inflate(layoutId, root, false);
}
File: activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent" >
<fragment
android:id="#+id/ActionBarCompatFragment"
android:layout_width="#dimen/ActionBarSize"
android:layout_height="match_parent"
android:layout_alignParentBottom="true"
android:layout_alignParentLeft="true"
android:layout_alignParentTop="true"
class="com.packagename.app.ActionBarCompatFragment" >
<!-- Preview: layout=#layout/fragment_actionbarcompat -->
</fragment>
<fragment
android:id="#+id/ComposerFragment"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:layout_alignParentRight="true"
android:layout_alignParentTop="true"
android:layout_toRightOf="#id/ActionBarCompatFragment"
class="com.packagename.app.ComposerFragment" >
<!-- Preview: layout=#layout/fragment_composer -->
</fragment>
</RelativeLayout>

findViewById crashing when using a custom view with <merge> and <include> tags

I am trying to make a custom view using XML then retrieve its properties and edit them. This custom view is added into the test2 view several times with different id's for each. I have tried to retrieve the id for one of these custom views (player_1) then get the TextView (player_name), and edits its properties and no other views like the player_2 id view.
But I am not sure that this is this even possible? It renders fine, but I get an error every time I try to do this in code. I'm really not sure how to approach this, I can't seem to get inflate to work either to test that instead. Any ideaS?
Thanks!
Game.java
setContentView(R.layout.test2);
View player1 = (View) findViewById(R.id.player_1);
TextView player1Name = (TextView) player1.findViewById(R.id.player_name);
player1Name.setText("John");
test2.xml
<include
layout="#layout/player_view"
android:id="#+id/player_1"
/>
<include
layout="#layout/player_view"
android:id="#+id/player_2"
/>
player_view.xml
<?xml version="1.0" encoding="utf-8"?>
<merge
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:player="http://schemas.android.com/apk/res/com.example.android.merge">
<LinearLayout
android:id="#+id/top_view"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="horizontal"
>
<TextView
android:id="#+id/player_name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="TextView"
>
</TextView>
</LinearLayout>
</merge>
This is a cut down version of player_view.xml, I can post more if needed.
Also I have not worked out if it even prints the error message for this in eclipse, it goes into debug mode and does not explain any further what the issue is.
Your player_view.xml has unknown type, so the include is confused what kind of View is it. Try to do the next:
Use or create new PlayerView class, mergeable with player_view.xml.
Edit test2.xml to:
<PlayerView
android:id="#+id/player_1"
/>
<PlayerView
android:id="#+id/player_2"
/>
then inflate it in your code as following:
setContentView(R.layout.test2);
PlayerView player1 = (PlayerView) findViewById(R.id.player_1);
LayoutInflater inflate = (LayoutInflater) getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE);
inflate.inflate(R.layout.player_view, player1);
Do the same for player2.

Categories