Add fragment into listview item
Asked Answered
R

6

11

I want to have a fragment for each item in a listview, because I want to separate some logic out. I am using a view holder for each item. If the view doesn't exist, I create a new fragment and add it into the container.

holder.mMyFragment = new MyFragment(mActivity, this);
mActivity.getSupportFragmentManager().beginTransaction().add(R.id.my_container, holder.mMyFragment).commit();

Also for each item, I call holder.mMyFragment.setUi(dataSource, position) to set UI of the fragment based on the data source and position.

The problem I'm having is I initialize the UI elements of fragment in onCreateView method of the fragment class, but it's not called when I add the fragment to the item. So later when I call setUi() which uses some UI elements in fragment it complains a NullPointerException. Does anyone have a suggestion? Thanks!

Raindrop answered 5/9, 2013 at 20:27 Comment(1)
It's late but @Brabbeldas ask me about how I got it, so I updated my answer. I'm using it in a private app.Tidings
L
12

I want to have a fragment for each item in a listview, because I want to separate some logic out.

You can't use fragment as list item views because the API doesn't allow you - View and Fragment aren't even related so there's no way you can use it like that. Make custom views and use adapter getViewTypeCount and getView to use different list item behavior.

Fragment are managed by Activity's FragmentManager or by other Fragments child FragmentManager; while list item views are managed by ListView & ListAdapter. You can use ListViews in Fragments, but not the other way around.

Linalinacre answered 5/9, 2013 at 20:32 Comment(2)
No. WE CAN USE Fragments as a list item . Please find my answer below and Vote :)Monticule
API allows to add Fragment in a View. So If listView has list of Views, it can have list of Fragments too.Noel
M
13

"THERE IS A SOLUTION" for this.

The issue is, you cannot add fragment directly to the container(FrameLayout) with same "id" in listview as you have done in the above code.

The trick is, create listview(Recyclerview) of "LinearLayout". Then dynamically create FrameLayout in adapter and assign different id's for each. Inflate Fragment to FrameLayout and this FrameLayout to LinearLayout.

@Override
protected void onBindBasicItemView(RecyclerView.ViewHolder holder, int position) {
    if (holder instanceof VideoHomeViewHolder) {
        VideoHomeViewHolder videoHomeViewHolder = (VideoHomeViewHolder) holder;
        FrameLayout frameLayout = new FrameLayout(mContext);
        frameLayout.setId(position+1); //since id cannot be zero
        FragmentHelper.popBackStackAndReplace(mFragmentManager, frameLayout.getId(),
                new ShowLatestVideosFragment(mShowLatestVideosItems.get(position)));
        videoHomeViewHolder.linearLayout.addView(frameLayout);
    }
}
Monticule answered 12/5, 2016 at 10:19 Comment(3)
you solution is working but there is an exception sometimes No view found for id 0x2 for fragmentResidency
in my case i am data list is updated and there is no constant time for as it reflects the changes from server , like item might be added or removed so i have to update adapter accordingly i am suing notify item inserted , notifyitemremoved and notifyrangeupdated as wellResidency
what inside the FragmentHelper.popBackStackAndReplace() function?Nepali
L
12

I want to have a fragment for each item in a listview, because I want to separate some logic out.

You can't use fragment as list item views because the API doesn't allow you - View and Fragment aren't even related so there's no way you can use it like that. Make custom views and use adapter getViewTypeCount and getView to use different list item behavior.

Fragment are managed by Activity's FragmentManager or by other Fragments child FragmentManager; while list item views are managed by ListView & ListAdapter. You can use ListViews in Fragments, but not the other way around.

Linalinacre answered 5/9, 2013 at 20:32 Comment(2)
No. WE CAN USE Fragments as a list item . Please find my answer below and Vote :)Monticule
API allows to add Fragment in a View. So If listView has list of Views, it can have list of Fragments too.Noel
P
3

A simple way. One problem:You should store add restore fragment state.

holder.mMyFragment = new MyFragment(mActivity, this);
int id = View.generateViewId();
findViewByTag("abc").setId(id);
mActivity.getSupportFragmentManager().beginTransaction().add(id, holder.mMyFragment).commit();
Pyo answered 29/3, 2016 at 9:48 Comment(0)
T
2

Hi I was facing the same problem and I found the way to do it.
My problem was similar to you:

"I want to have a fragment for each item in a listview, because I want to separate some logic out"

In my app I have to give the option to display custom items in vertical (listView) and horizontal (ViewPager) mode. Additionally I had to deal with 18 custom items and each one with different logic, so the best approach was reusing the fragments that I was using for ViewPager in ListView.

I got it but not in the way you were trying, I mean, I used my fragments like "ViewHolders":

  1. Define fragment's widget like variables of class in each fragment.
  2. Create a custom ArrayAdapter and override: getViewTypeCount(), getItemViewType(int position), getCount(), getItem(int position) getView(int position, View convertView, ViewGroup parent)

In getView I checked what kind of layout I needed before "inflate" the respective XML, create a fragment, assign widget from XML to fragment (with rootView.findViewById) and set "tag" with the new fragment.

What you can see at this point is that fragments in ListView never got attached to Activity but you got what you wanted: logic distributed in several parts and all benefits of ListView (reuse of rows, scroll, etc).

If you need I can post part of my code but you have to deal with "spanglish" ;).



UPDATED

All the problem is because when you create a Fragment to be used with ViewPager, usually all "layout and "setup" code is inside onCreateView method, I mean:

  • Get the view object you are going to use (View rootView = inflater.inflate(R.layout.fragment_question_horizontal_container, container, false);)
  • Get the widgets from above layout, define behaviors, fonts, etc: (answer = (EditText)rootView.findViewById(R.id.answer_question_text);)

Until this point there is nothing weird.
If you are going to use a fragment with the behavior described above you have to "emulate" the call to onCreateView, fill the data and attach it to the listView.

Here is the trick: split the code in onCreateView in some methods that doesn't care about who's calling them. An example of my onCreateView:

@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
                         Bundle savedInstanceState) {

    View rootView = inflater.inflate(R.layout.fragment_pregunta_horizontal_container, container, false);

    addAnswerLayout(rootView, R.layout.fragment_pregunta_texto, getActivity());
    setUpComponents(rootView);
    //those 2 methods are about my app logic so I'm not going to talk much about them
    setUpQuestionState(savedInstanceState);
    readSavedAnswer();

    return rootView;
}

public void addAnswerLayout(View rootView, int optionId, Context context) {

    mContext = context;

    RelativeLayout relativeLayout = (RelativeLayout)rootView.findViewById(R.id.pregunta_container);

    LayoutInflater inflater = ((Activity)mContext).getLayoutInflater();
    View newView = inflater.inflate(optionId, relativeLayout, false);

    relativeLayout.addView(newView);
}

public void setUpComponents(View rootView) {
    //next line: some heritage that you can ignore
    super.setUpComponents(rootView);

    respuesta = (EditText)rootView.findViewById(R.id.pregunta_respuesta_texto);
    respuesta.setTypeface(UiHelper.getInstance(getActivity()).typeface);
    respuesta.setTextColor(mContext.getResources().getColor(R.color.drs_gris));

    ...
}


Now let's go to the CustomArrayAdapter for list view:

  • Define your customArrayAdapter something like this: PreguntasVerticalArrayAdapter extends ArrayAdapter<Pregunta> where "Pregunta" is a generic Fragment with the logic from above.
  • Override getViewTypeCount(), getItemViewType(int position), getCount(), getItem(int position) getView(int position, View convertView, ViewGroup parent).

The getView follow the same behavior: get the object for the given position in params, reuse a "viewholder" and fill the data. Here my getView:

@Override
public View getView(int position, View convertView, ViewGroup parent) {

    View rowView = convertView;

    Pregunta pregunta = mData.get(position);

    if (rowView == null)
        rowView = createQuestionUI(pregunta, parent, position);

    fillDataInRow((PreguntaUI)rowView.getTag(), pregunta, position);

    return rowView;
}

private View createPreguntaUI(Pregunta pregunta, ViewGroup parent, int position) {

    View rootView = null;

    LayoutInflater inflater = (mPreguntasVerticalFragment.getActivity()).getLayoutInflater();

    //you can ignore this part of the code ralted to Bundle.
    Bundle args = new Bundle();
    args.putLong(PreguntaUI.PREGUNTAUI_ID, pregunta.getIdpregunta());
    args.putInt(PreguntaUI.PREGUNTAUI_INDEX, position);
    args.putInt(PreguntaUI.PREGUNTAUI_TOTAL_QUESTIONS, getCount());

    //internal method of "pregunta" to know what kind of question it is.
    String tipo = pregunta.getTipo();

    if (tipo.equalsIgnoreCase(PreguntaType.TEXT.toString())) {

        rootView = inflater.inflate(R.layout.fragment_pregunta_vertical_container, parent, false);

        Pregunta_texto pregunta_texto = new Pregunta_texto();
        pregunta_texto.setArguments(args);

        //LOOK AT THIS POINT!!!: I'm calling the same methods that I called in onCreateView fragment's method.
        pregunta_texto.addAnswerLayout(rootView, R.layout.fragment_pregunta_texto,
                mPreguntasVerticalFragment.getActivity());
        pregunta_texto.setUpComponents(rootView);
        pregunta_texto.setUpQuestionState(null);
        pregunta_texto.readSavedAnswer();

        //I'm adding the fragment to reuse it when I can
        rootView.setTag(pregunta_texto);
    }
    else if ...

    return rootView;
}



That is all... at this point, if you have enough experience dealing with CustomArrayAdapters and Fragments you probably got it! :D

Tidings answered 14/9, 2014 at 0:44 Comment(4)
Hi 0mahc0, yes please, could you post some of your logic?Quarter
Sure, give me somedays (weekend)Tidings
I am curious how you got it working because lots of people say it is not possible to add fragments into a ListView, e.g. here: #11363809Quarter
@Quarter Ready. I updated my answer. Hope it can help you and others.Tidings
V
-1

From the Android Documentation : "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."

For your activity, do you want to add 2 fragments where the first one displays a listView (= ListFragment and the other one is in the right and is shown only when the user clicks on an item (from the first fragment or listView) ?

Victorious answered 5/9, 2013 at 20:42 Comment(0)
W
-3

Instead of using ListFragment, you can use RecyclerView, Android has documentation on that:

enter image description here

https://developer.android.com/training/basics/fragments/creating.html#AddInLayout

Woodyard answered 21/8, 2019 at 19:24 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.