1

Diagram of Hierarchical View consisting of Android Activity with Fragments

This diagram illustrates the complexity of my activity. There are multiple (3 in this case) layers of fragments that need to load dynamically. The repeating fragments are loaded using LinearListView, a view library I found here: https://github.com/frankiesardo/LinearListView. This allows the list to load like a ListView, but avoids the problems of having a ListView inside a ScrollView.

Here is some of the sample code:

line_item_list.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical" android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="@+id/line_item_list">

  <TextView
    android:layout_width="171dp"
    android:layout_height="wrap_content"
    android:text="LineItem List"
    android:id="@+id/line_item_list_text" />

  <com.linearlistview.LinearListView
    android:id="@+id/line_item_wrapper"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:divider="#f00"
    android:orientation="vertical"
    android:showDividers="middle"
    app:dividerThickness="16dp"
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/tools"/>
</LinearLayout>

line_item.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="@+id/line_item">

  <TextView
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:text="my line item."
    android:id="@+id/line_item_text"
    android:layout_gravity="center_horizontal" />

  <!-- notes child items go here -->

</LinearLayout>

LineItemFragment.java loads the fragment with an adapter (lineItem in this code is displayed with the word, "Part" in the illustration above.)

public class LineItemFragment extends Fragment {
  LineItemAdapter adapter;

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


    LinearListView lineItems = (LinearListView)container.findViewById(R.id.line_item_wrapper);
    adapter = new LineItemAdapter(this.getContext(), getChildFragmentManager());
    lineItems.setAdapter(adapter);

    return null;

  }
}

LineItemAdapter.java

public class LineItemAdapter extends BaseAdapter{

  ArrayList<String> lineItems = new ArrayList<String>();
  Context context;
  FragmentManager fm;


  public LineItemAdapter(Context context, FragmentManager fragmentManager) {
    this.context = context;
    lineItems.add("Item A");
    lineItems.add("Item B");
    lineItems.add("Item C");
    this.fm = fragmentManager;
  }

  @Override
  public int getCount() {
    return lineItems.size();
  }

  @Override
  public Object getItem(int position) {
    return lineItems.get(position);
  }

  @Override
  public long getItemId(int position) {
    return position;
  }

  @Override
  public View getView(int position, View convertView, ViewGroup parent) {
    if (convertView == null) {
        convertView = ((LayoutInflater)context.getSystemService(Context.LAYOUT_INFLATER_SERVICE))
                .inflate(R.layout.line_item, parent, false);
    }
        ((TextView)convertView.findViewById(R.id.line_item_text)).setText(getItem(position).toString());


    // *** The getView method could load the child fragments
    return convertView;
  }
}

In this previous file (***), I am currently thinking the getView method should load the next level child fragment, but everything I have tried has not worked. It appears that the layout is not yet inflated, and so I cannot add child views to it.

My question is, "how do i add child fragments from within the getView?" Or maybe I am going about this wrong.

Here is one example of what I have tried:

getView Method (***) in lineItemAdapter.java:

.
.
.

    LinearLayout notes = (LinearLayout) ((LayoutInflater)context.getSystemService(Context.LAYOUT_INFLATER_SERVICE))
            .inflate(R.layout.line_item, parent, false);

    LinearLayout b = (LinearLayout)((LayoutInflater)context.getSystemService(Context.LAYOUT_INFLATER_SERVICE))
            .inflate(R.layout.note_list, notes).getRootView();

    NoteListFragment noteListFragment = new NoteListFragment();
    fm.beginTransaction().add(b.getId(), noteListFragment).commit();
.
.
.

And I got an exception like this.

E/AndroidRuntime: FATAL EXCEPTION: main

Process: com.example.myapplication, PID: 12207

java.lang.RuntimeException: Unable to start activity   
ComponentInfo{com.example.myapplication/com.example.myapplication.DetailActivity}: java.lang.IllegalStateException: Fragment does not have a view
                                                                            at 
android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2416)
                                                                            at   
android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2476)
.
.
.

Thanks in advance.

EDIT

Do I need to add a fragment to the xml, i.e.:

line_item.xml...new node:

<fragment android:name="com.example.noteListFragment"
          android:id="@+id/note_list_fragment"
          android:layout_weight="1"
          android:layout_width="0dp"
          android:layout_height="wrap_content" />
dwaz
  • 644
  • 1
  • 8
  • 21
  • You cant add fragments within `getView()` which belongs to a Fragment. You create an add a fragment from your Activity. – mt0s Feb 17 '16 at 06:00
  • Check this answer also http://stackoverflow.com/a/13221546/423980 – mt0s Feb 17 '16 at 06:04
  • Appreciate the quick responses. Helpful, but how would you address the complexity of multiple layers requesting fragments from the activity? Would I be able to pass convertview to the method in the Activity without causing a memory leak so that the children could be added? – dwaz Feb 17 '16 at 06:13
  • What I do in a case like that is I use an event bus library (like [Otto](http://square.github.io/otto/) ). I have my Activity subscribed to events and when these happens you react accordingly (by replacing a fragment with another one). – mt0s Feb 17 '16 at 06:17
  • Thanks @mt0s. I will give it a shot. – dwaz Feb 17 '16 at 06:47
  • Sorry, but I need more clarification. Based on above, I would need access to a child fragment, but still don't see how that is created. Can you provide an example? – dwaz Feb 17 '16 at 18:17

2 Answers2

0

In the end I moved away from fragments for this purpose, and removed the inflation code. Instead, I load up the same layout structure with includes in the xml, so I don't have to build larger xml files than I already have.

For the functionality I would have put in the fragments, I placed in presenter classes, then used view injection from ButterKnife to tie the presenter to the appropriate levels in the views.

I am still working on using an event bus to communicate between the layers like @mt0s suggested.

Here is some code to help you out if you want to go this direction:

Using includes I can bring the entire structure together, keeping each layer layout in its own xml file:

<LinearLayout 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/activity_workorder"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="true"
>
    <LinearLayout
        android:id="@+id/workorder_detail_container"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical">
        <include layout="@layout/workorder_header"/> <!-- include other layouts -->
        <include layout="@layout/workorder_service_items"/>
    </LinearLayout>
</LinearLayout>

Next I created a Presenter.class file to set up the adapter, using ButterKnife to bind to that portion of the view:

public class WorkOrderPresenter implements PropertyChangeListener
{

    private View detailView;
    private WorkOrder workOrder; // the object to display
    private List<WorkorderAsset> assetList;

    // Set your bindings to the view, passing the id from the layout.
    @Bind(R.id.workorderasset_list) View serviceItemDetail;
    @Bind(R.id.workorderasset_list_content) LinearListView assets;
    @Bind(R.id.number_of_service_items) TextView numberOfServiceItems;
    @Bind(R.id.total_value) TextView totalView;

    WorkOrderAssetAdapter workOrderAssetAdapter; // an adapter for a child list

    // Constructor
    public WorkOrderPresenter(View view, WorkOrder wo, final Activity activity)
    {
        workOrder = wo;
        detailView = view;
        ButterKnife.bind(this, detailView); //this is where the binding actually happens

        workOrderAssetContent = Provider.GetItems // Load you items for the adapter.

        // My system gives items back asynchronously:
        workOrderAssetContent.getWorkOrderAssets(workOrder.getWorkOrderID(), new iProvideCallback<List<WorkorderAsset>>() {
            @Override
            public void onSuccess(iResponse<List<WorkorderAsset>> response) {
                assetList = response.getResponse();
                numberOfServiceItems.setText(String.valueOf(assetList.size()));
                workOrderAssetAdapter = new WorkOrderAssetAdapter(activity, assetList); // construct the adapter with items.
                assets.setAdapter(workOrderAssetAdapter);
            }

            @Override
            public void onFailure(iResponse<List<WorkorderAsset>> response) {
                Toast.makeText(activity.getApplicationContext(), R.string.ServiceItemsError, Toast.LENGTH_LONG);

            }
        });
    }
}

And here is an adapter to display the items:

public class WorkOrderAssetAdapter extends BaseAdapter
{
    private Activity activity;
    private List<WorkorderAsset> workorderAssetList;
    private static LayoutInflater inflater = null;

    // Constructor
    public WorkOrderAssetAdapter(Activity a, List<WorkorderAsset> items)
    {
        workorderAssetList = items;
        activity = a;
        inflater = LayoutInflater.from(activity);
    }

    @Override
    public int getCount() { return workorderAssetList.size(); }

    @Override
    public Object getItem(int position) { return workorderAssetList.get(position); }

    @Override
    public long getItemId(int position) { return position; }

    // view holder for the item
    public static class ViewHolder extends WorkOrderAssetViewHolder
    {
        @Bind(R.id.line_item_text) TextView line_item_text;
        @Bind(R.id.line_item_list) LinearListView list;

        // Adapter, Data Provider and Presenter for the next level.
        public WorkOrderAssetLineItemAdapter adapter;
        public WorkOrderAssetLineItemProvider provider;
        public WorkOrderAssetLineItemPresenter presenter;

        private Activity activity;
        private WorkorderAsset workOrderAsset;

        public VehicleViewHolder(WorkorderAsset asset, final View view, Activity activity_in)
        {
            ButterKnife.bind(this, view);
            workOrderAsset = asset;
            activity = activity_in;
            workOrderAssetLineItemContent = new WorkOrderAssetLineItemContent(((RoadFS)activity.getApplication()).getServer(), ((RoadFS)activity.getApplication()).getApplicationContext());
            workOrderAssetLineItemContent.getWorkOrderAssetLineItems(
                    workOrderAsset.getWorkOrderID(),
                    workOrderAsset.getWorkOrderAssetID(),
                    new iProvideCallback<List<WorkorderAssetLineItem>>() {

                        @Override
                        public void onSuccess(iResponse response) {
                            list.setAdapter(new WorkOrderAssetLineItemAdapter(activity,
                                    (List<WorkorderAssetLineItem>) response.getResponse(),
                                    view));
                        }

                        @Override
                        public void onFailure(iResponse response) {

                        }
                    });

        }

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

        WorkorderAsset workOrderAsset;
        workOrderAsset = workorderAssetList.get(position);
        ViewHolder holder;
        if (view != null) {
            holder = (ViewHolder) view.getTag();
        } else {
            view = inflater.inflate(R.layout.workorder_detail_workorderasset_vehicle, parent, false);
            holder = new VehicleViewHolder(workOrderAsset, view, activity);
            view.setTag(holder);
        }

        holder.item_text.setText(workOrderAsset.getVIN());

        holder.workOrderAssetLineItemContent = new WorkOrderAssetLineItemContent(((RoadFS) activity.getApplication()).getServer(), activity);
        holder.workOrderAssetLineItemAdapter = new WorkOrderAssetLineItemAdapter(activity, WorkOrderAssetLineItemContent.workorderAssetLineItemList, view);

        return view;
    }
}

This example is a little incomplete, and I don't yet have a sample project, but hopefully it will help someone out. It doesn't answer the question as expected, but having tried both ways, I think it is a better solution because it is simpler than dealing with the complexities of fragments. Fragments will still be used for changing screen size/orientation, but fragments will not be used for the complex layout. Thanks for any input.

dwaz
  • 644
  • 1
  • 8
  • 21
0
  1. Use different class for adapter

    public class TabCategory extends Fragment {
        private ListView listView;
        @Override
        public void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
        }
        @Override
        public View onCreateView(LayoutInflater inflater, ViewGroup container,   Bundle savedInstanceState) {
            // Inflate the layout for this fragment
            View tabCat=inflater.inflate(R.layout.tab_all, container, false);
            listView = (ListView) tabCat.findViewById(R.id.listView);
            //ADAPTER
            CategoryList adapter = new CategoryList(getContext());
            listView.setAdapter(adapter);
            return tabCat;
        }
    }
    
Cà phê đen
  • 1,883
  • 2
  • 21
  • 20
Akash Ingle
  • 161
  • 1
  • 12