id attribute in fragments - android

I am working on a app and it runs just fine but there is an problem that it crashes when I dont write id in my fragment code but runs fine after using id attribute and the problem is that i have not used it's id anywhere in my app
Here is the xml file
<?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:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="horizontal"
>
<fragment
class="com.hfad.workout.WorkoutList"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="2"
android:id="#+id/listfrag"
/>
<FrameLayout
android:layout_width="0dp"
android:layout_height="match_parent"
android:text="second text view"
android:layout_marginTop="20dp"
android:id="#+id/fragcont"
android:layout_weight="3"
/>
</LinearLayout>
workoutlist.jav
public class WorkoutList extends ListFragment {
MainActivity ma;
static interface WorkoutListListener{
void clickme(long at);
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
ArrayAdapter<Workout> arrayAdapter = new ArrayAdapter<Workout>(
inflater.getContext(), android.R.layout.simple_list_item_1,
Workout.workout);
setListAdapter(arrayAdapter);
return super.onCreateView(inflater, container, savedInstanceState);
}
#Override
public void onAttach(Context ac){
super.onAttach(ac);
this.ma =(MainActivity) getActivity();
}
public void onListItemClick(ListView v, View vi, int position, long id){
ma.clickme(id);
}
}

Before an activity can talk to its fragment, the activity first needs
to get a reference to it. To get a reference to the fragment, you first
get a reference to the activity’s fragment manager using the
activity’s getFragmentManager() method. You then use its
findFragmentById() method to get a reference to the fragment:
findFragmentById() is a
bit like findViewById()
except you use it to get a
reference to a fragment
getFragmentManager().findFragmentById(R.id.fragment_id)

It's because if you do not specify fragment id, then FragmentManager cannot restore it after recreating.
You have to specify fragment id or tag.

Related

Floating action button causing memory leak

I have an app am working on, the app is using leak canary to detect possible memory leaks. The app seems to be fine except for my extended floating action button, according to leak canary the button is leaking, I do not have an idea on how to rectify this.
Below is my XML
<?xml version="1.0" encoding="utf-8"?>
<androidx.coordinatorlayout.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:id="#+id/coordinator"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".fragments.store.StoreFragment">
<com.google.android.material.card.MaterialCardView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginTop="#dimen/small_margin"
app:cardElevation="#dimen/normal_elevation"
app:shapeAppearanceOverlay="#style/ShapeAppearanceOverlayLargeCutLeftTopCorner">
<androidx.swiperefreshlayout.widget.SwipeRefreshLayout
android:id="#+id/swipeToRefresh"
android:layout_width="wrap_content"
android:layout_height="wrap_content">
<androidx.core.widget.NestedScrollView
android:id="#+id/nestScrollView"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fadingEdge="vertical">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<ViewStub
android:id="#+id/layoutCategories"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:inflatedId="#+id/panel_layoutCategories"
android:layout="#layout/store_layout_categories" />
<ViewStub
android:id="#+id/layoutDeals"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:inflatedId="#+id/panel_layoutDeals"
android:layout="#layout/store_layout_deals" />
<ViewStub
android:id="#+id/layoutCollections"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:inflatedId="#+id/panel_layoutCollections"
android:layout="#layout/store_layout_collections" />
</LinearLayout>
</androidx.core.widget.NestedScrollView>
</androidx.swiperefreshlayout.widget.SwipeRefreshLayout>
</com.google.android.material.card.MaterialCardView>
<com.google.android.material.floatingactionbutton.ExtendedFloatingActionButton
android:id="#+id/btnGoToCart"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="bottom|end"
android:text="#string/cart"
android:textColor="#android:color/white"
android:textStyle="bold"
app:backgroundTint="#color/colorAccent"
app:elevation="#dimen/large_elevation"
app:icon="#drawable/ic_shopping_cart_24px"
app:iconTint="#android:color/white"
app:layout_anchor="#id/nestScrollView"
app:layout_anchorGravity="bottom|end"
app:shapeAppearanceOverlay="#style/ShapeAppearanceOverlayLargeCutLeftTopCorner" />
</androidx.coordinatorlayout.widget.CoordinatorLayout>
And my Java code
public class StoreFragment extends Fragment implements View.OnClickListener {
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
// Inflate the layout for this fragment
return inflater.inflate(R.layout.fragment_store, container, false);
}
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
}
#Override
public void onViewCreated(#NonNull View view, #Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
ButterKnife.bind(this, view);
btnGoToCart.setOnClickListener(this);
}
#Override
public void onClick(View v) {
switch (v.getId()) {
....
case R.id.btnGoToCart:
Navigation.findNavController(v).navigate(R.id.cartFragment);
break;
....
}
}
}
Below is a snippet from leak canary. At first the leak was pointing to the ID of the coordinator layout, I removed it, now it is pointing to the Extended floating action button.
Instead of having the Fragment implement OnClickListener why not just create a new OnClickListner inner class? From what I see you ar passing the fragment as the OnClickListener and the view later holds a reference to your fragment and that's probably the cause of the leak. Just do (new OnClickListener () {}); instead of implementing OnClickListener.
EDIT:
I've just noticed that you are using ButterKnife in a Fragment.
If your using ButterKnife in a Fragment you should use:
private Unbinder unbinder;
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.fancy_fragment, container, false);
unbinder = ButterKnife.bind(this, view);
// TODO Use fields...
return view;
}
#Override public void onDestroyView() {
super.onDestroyView();
unbinder.unbind();
}
BINDING RESET Fragments have a different view lifecycle than
activities. When binding a fragment in onCreateView, set the views to
null in onDestroyView. Butter Knife returns an Unbinder instance when
you call bind to do this for you. Call its unbind method in the
appropriate lifecycle callback.

Error inflating ListFragment from XML file

I am building an Android app using Fragments. In the XML file for one of my Fragments I simply have one ListFragment and one Button, like this (the FilteredRecipesListFragment is extending ListFragment):
<?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"
android:orientation="vertical" >
<com.mycompany.myapp.gui.FilteredRecipesListFragment
android:id="#+id/filtered_recipes_list_fragment"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:focusable="true" />
<Button
android:id="#+id/show_recipe_filter_dialog_button"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="#string/show_recipe_filter_dialog_button"
android:onClick="showFilter"
/>
</LinearLayout>
Fairly simple stuff. Furthermore the class FilteredRecipesFragment that is to inflate this XML file looks like this:
public class FilteredRecipesFragment extends Fragment {
private FilteredRecipesListFragment mFilteredRecipesListFragment;
private Button mShowRecipeFilterButton;
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View rootView = inflater.inflate(R.layout.filtered_recipes_fragment, container, false);
mFilteredRecipesListFragment = (FilteredRecipesListFragment) getFragmentManager().findFragmentById(R.id.filtered_recipes_list_fragment);
mShowRecipeFilterButton = (Button) rootView.findViewById(R.id.show_recipe_filter_dialog_button);
showRecipeFilterButton.setOnClickListener(new RecipeFilterButtonListener());
return rootView;
}
}
Should also be straightforward. The problem is at the line inflating FilteredRecipesListFragment. Here I get NoSuchMethodException: FilteredRecipesListFragment(Context, AttributeSet), because I have not implemented that constructor in my FilteredRecipesListFragment class. I am not sure why I would need that, since calling super(Context, AttributeSet) is not an option in Fragments as in Views.
And this is probably where I am heading wrong; am I approaching the whole concept of Fragments wrong in this case? Is it better practice or does it make more sense to use a ListView instead of a custom ListFragment inside another Fragment? If this is ok using the FilteredRecipesListFragment(Context, AttributeSet) method, how should I use this to make the class inflatable?
Here is my FilteredRecipesListFragment for reference:
public class FilteredRecipesListFragment extends ListFragment
{
private FilteredRecipesListAdapter mRecipeAdapter;
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)
{
mRecipeAdapter = new FilteredRecipesListAdapter(getActivity(), null);
return super.onCreateView(inflater, container, savedInstanceState);
}
#Override
public void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
}
#Override
public void onPause()
{
super.onPause();
}
#Override
public void onListItemClick(ListView l, View v, int position, long id)
{
super.onListItemClick(l, v, position, id);
}
}
Try looking at this.
Look at how you should declare a fragment in XML:
<fragment android:name="com.example.android.fragments.HeadlinesFragment"
android:id="#+id/headlines_fragment"
android:layout_width="match_parent"
android:layout_height="match_parent" />

having a condition to fragment adding

for the last 4 hours ive been trying to understand how to do it.
to be clear, i need to add a fragment under a certain condition and for my purpose the condition can be either:
a. if the parent's id matches to what i seek.
b. if the fragment's id matches to what i seek.
i tried to put the condition inside the fragment itself:
public class BlockFrag extends Fragment{
SharedPreferences sp;
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)
{
sp = getActivity().getSharedPreferences("myOptions", Context.MODE_PRIVATE);
int id = container.getId();//didn't work(i think the container is null for some reason
switch (id)
{
default: return inflater.inflate(R.layout.nothing, container, false);
case (R.id.redFrame): {
if (sp.getBoolean("redBlock", true)) {
return inflater.inflate(R.layout.block_frag, container, false);
}
}
case (R.id.greenFrame): {
if (sp.getBoolean("greenBlock", true)) {
return inflater.inflate(R.layout.block_frag, container, false);
}
i also tried to get the condition programatically but i cant understand how to prevent the fragment to automatically generate when i call the layout it's placed.
this is the layout it's placed for example
<FrameLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="#+id/redFrame"
android:layout_weight="0.33333">
<fragment
android:name="com.example.dor.myapplication.BlockFrag"
android:id="#+id/blockRed"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:layout="#layout/block_frag"/>
this is the fragment's layout if it matters...
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_alignParentLeft="true"
android:layout_alignParentStart="true">
<ImageView
android:layout_width="118dp"
android:layout_height="match_parent"
android:id="#+id/blockFrag"
android:src="#drawable/block"
android:layout_weight="0.33333"
android:layout_above="#+id/linearLayout"
android:layout_alignParentLeft="true"
android:layout_alignParentStart="true"
android:layout_gravity="left"
android:scaleType="fitXY"/>
thank you for helping, any kind of way to do this will help me.
I did something similar earlier, I had to decide based on a value from PickHeaderFragment which Fragment to show inside my root_frame_body, here's how I achieved it:
My Parent or Host Fragment:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:background="#000"
android:layout_height="wrap_content"
android:id="#+id/root_frame_header" />
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:background="#000"
android:layout_weight="1"
android:layout_height="match_parent"
android:id="#+id/root_frame_body" />
</LinearLayout>
Inside my Host Fragment:
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
View rootView = inflater.inflate(R.layout.fragment_pick, container, false);
headerFragment = PickHeaderFragment.newInstance("");
getChildFragmentManager().beginTransaction().replace(R.id.root_frame_header, headerFragment).commit();
}
My PickHeaderFragment has an OnPickHeaderFragmentInteractionListener Interface implemented which looks like this:
public interface OnPickHeaderFragmentInteractionListener {
public void onFragmentInteraction(int res);
}
I ensure my Host Fragment will implement this interface by adding it during onAttach (inside PickHeaderFragment) like so:
#Override
public void onAttach(Activity activity) {
super.onAttach(activity);
try {
Fragment f = getParentFragment();
mListener = (OnPickHeaderFragmentInteractionListener) getParentFragment();
} catch (ClassCastException e) {
throw new ClassCastException(activity.toString() + " must implement OnFragmentInteractionListener");
}
}
Now, when the enter Button inside my PickHeaderFragment is clicked I call this event to let my Host Fragment know to change the root_frame_body:
Inside my Host Fragment I have:
#Override
public void onFragmentInteraction(int res) {
if (res == R.id.btnEnter) {
getChildFragmentManager().beginTransaction().replace(R.id.root_frame_body, new PickByItemBodyFragment()).commit();;
}
}
I am now able to change the layout (or which Fragment is displayed) in my Host Fragment after an event occurs inside a child Fragment.
Note: In my example I have nested Fragments, but you could just as easily have several Fragments within an Activity.
In summary I would recommend you follow a similar pattern of having a host Activity or Fragment and ensuring you have a FrameLayout added to your view to allow you to inflate whatever Fragment you need.
An added benefit to doing it this way is that you can segregate your code into separate Fragments also.
Leigh thank you for your answer but i don't know if you didn't understand my question or i didn't understand your answer...
anyway, i solved my problem through programatically adding the fragment in r=the onCreate of the activity and inserting the adding to an "if"
BlockFrag rb = new BlockFrag();
FragmentManager fragmentManager = getFragmentManager ();//declaring Fragment Manager
FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction ();// declaring Fragment Transaction
if(condition)
{
editor.putBoolean("redBlock", true);
fragmentTransaction.add(R.id.redFrame, rb);//adding the fragment
}
needless to say i didn't declare the fragment in my activity layout and didn't perform any changes in the fragment class or fragment xml layout.

Android Fragment - Instantiation Exception in Layout

I have a layout which contains a <fragment> called MenuFragment. Whenever I launch the application howver, I get a Trying to instantiate a class com.usmaan.whackem.MenuFragment that is not a Fragment.
Layout:
<LinearLayout
android:layout_width="match_parent"
android:layout_height="fill_parent"
android:layout_below="#id/layoutHeader"
android:layout_marginTop="20dp">
<fragment android:name="com.usmaan.whackem.MenuFragment"
android:id="#+id/menu_fragment"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</LinearLayout>
Fragment:
public class MenuFragment extends Fragment{
public MenuFragment() {
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
return inflater.inflate(R.layout.menu_fragment, container, false);
}
}
I've tried
1) Adding an ID to the <fragment>
2) Adding a namespace to <fragment>
I fixed the issue by extending the Activity which holds the fragment from FragmentActivity and not Activity.

Null Object that is a child of a Fragment

I have a Fragment that is part of a ViewPager. In this Fragment I have a ViewGroup child. Now, why in my MainActivity's onCreate() after having instantiated my ViewPager and adapter, my Container is getting null?
Here is my onCreate():
private MyAdapter mAdapter;
private ViewPager mPager;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mAdapter = new MyAdapter(getSupportFragmentManager());
mPager = (ViewPager) findViewById(R.id.pager);
mPager.setAdapter(mAdapter);
mContainerView = (ViewGroup) findViewById(R.id.container);
//Here mContainerView is already null
...
}
Here is the Fragment that is part of a ViewPager that contains mContainerView
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/content_frame"
android:layout_width="match_parent"
android:layout_height="match_parent">
<ScrollView android:layout_width="match_parent"
android:layout_height="match_parent">
<!-- This is my ViewGroup -->
<LinearLayout android:id="#+id/container"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:showDividers="middle"
android:divider="?android:dividerHorizontal"
android:animateLayoutChanges="true"
android:paddingLeft="16dp"
android:paddingRight="16dp" />
</ScrollView>
<TextView android:id="#android:id/empty"
style="?android:textAppearanceSmall"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:padding="32dp"
android:text="#string/message_empty_layout_changes"
android:textColor="?android:textColorSecondary" />
</FrameLayout>
If I'm reading your question correctly, you're trying to access the Fragment's View (and children) using the Activity's findViewById() method. That shouldn't work since the Fragment inflates its own layout and Fragments are not traditional Views.
If you know the Fragment is instantiated and you can retrieve it, you can get an instance of the ViewGroup using
yourFragment#getView().findViewById()
If not, you can make an interface that your Activity implements with a method that accepts a ViewGroup as the argument. Then in the Fragment's onCreateView(), have the Fragment pass the ViewGroup off to the interface. You can cast directly to your Activity, but an interface is cleaner.
Eg
public class Fragment {
public interface ViewGroupCreateListener{
public void onViewGroupCreated (ViewGroup v);
}
private ViewGroupCreateListener listener;
public void onAttach (Activity a){
super.onAttach (a);
listener = (ViewGroupCreateListener) a;
}
public View onCreateView (/*all its arguments here*/){
View v = inflater.inflate (R.layout.your_layout);
ViewGroup group = v.findViewById (R.id.container);
listener.onViewGroupCreated(group);
return v;
}
}
Your Activity would look something like:
public class MainActivity extends Activity implements ViewGroupCreateListener, OtherInterface1, OtherInterface2{
private ViewGroup mViewGroup;
public void onViewGroupCreated (ViewGroup v){
mViewGroup = v;
}
}
This is nice since if the pager re-instantiates the Fragment, the Activity still gets a valid instance of the ViewGroup.
Or, if depending on what you're actually trying to achieve with this ViewGroup, you may be able to do this processing inside the Fragment itself.

Categories

Resources