2

Objective: I have one activity and within two fragments. The ideia is to communicate both fragments, selecting one category and updating its item list.On activity load, a category is settle by default.

Problem: On activity load, ItemsFragment doesn't display/update itemsLisView. Also ItemAdapter's getView Method is being called only twice, even though the list has five items.

Facts:

  1. Items List is being correctly fulfilled before being passed to adapter
  2. Breakpointing the code, not sure why or if it matters, ItemsFragment is being rendered/called before CategoriesFragments. I found this weird since CategoriesFragments is positioned above ItemsFragment. This is the reason why I wrote if (getArguments() != null). Before I was getting Null Pointer Exception.

Research: There is a lot of questions regarding both subjects.

  1. Android ListView not refreshing after notifyDataSetChanged with data as Map
  2. ListFragment Not Rendering and getView() in Adapter Not Being Called
  3. Android getView() not being called properly?
  4. BaseAdapter notifyDatasetChanged() called but getView() is never called

Those are some link examples (of thousands) I went through, trying to find a solution for my case. Unfortunately none of them worked.

I've been with this problem for almost 5 days now and I guess I missing some Android concept here. It's not something related with Java Programming, or at least I hope so.

Questions:

  1. Why ItemsFragment is not displaying Items List on activity load? (This probably answers updating problem)
  2. Why ItemAdapter just calls getView only twice, even that Items List is correctly given to him with more items?

For any further needed information, please don't mind to ask. Thanks in advance for any help.


EDIT - MCVE:

activity_triple_list:

<?xml version="1.0" encoding="utf-8"?>
<android.support.design.widget.CoordinatorLayout 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:fitsSystemWindows="true"
    tools:context=".controller.activities.Lists">

    <include layout="@layout/content_triple_list" />
</android.support.design.widget.CoordinatorLayout>

content_triple_list:

<?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">

    <android.support.v4.view.ViewPager
        android:id="@+id/listsPager"
        android:layout_width="match_parent"
        android:layout_height="match_parent">

        <android.support.v4.view.PagerTabStrip
            android:id="@+id/pager_header"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_gravity="top"
            android:paddingBottom="4dp"
            android:paddingTop="4dp" />

    </android.support.v4.view.ViewPager>
</LinearLayout>

fragment_categories:

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

    <TableLayout
        android:id="@+id/categoriesTable"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:gravity="center"
        android:stretchColumns="4">

        <TableRow
            android:layout_width="fill_parent"
            android:layout_height="match_parent"
            android:layout_marginTop="5dp"
            android:gravity="center_horizontal">

            <LinearLayout
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_column="0"
                android:layout_marginLeft="13dp"
                android:layout_marginRight="13dp"
                android:orientation="vertical">

                <TextView
                    android:id="@+id/category1"
                    android:layout_width="fill_parent"
                    android:layout_height="wrap_content"
                    android:layout_marginTop="5dp"
                    android:text="CATEGORY 1"
                    android:textAlignment="center"
                    android:textAppearance="?android:attr/textAppearanceSmall" />
            </LinearLayout>

        </TableRow>

    </TableLayout>
</LinearLayout>

fragment_items:

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

    <ListView
        android:id="@+id/itemsListView"
        android:layout_width="fill_parent"
        android:layout_height="fill_parent"
        android:divider="@null" />

</LinearLayout>

content_row_choose_item:

<?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:background="@drawable/custom_row"
    android:orientation="vertical"
    android:paddingBottom="5dp"
    android:paddingLeft="15dp"
    android:paddingTop="5dp">

    <TextView
        android:id="@+id/itemName"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="TEST"
        android:textAppearance="?android:attr/textAppearanceLarge" />
</LinearLayout>

custom_row:

<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android" android:background="#2fff00">    
    <item android:drawable="@color/colorPrimary" android:state_pressed="true" /> <!-- pressed -->       
</selector>

ApplicationUtils:

public final class ApplicationUtils extends Application {

    private static Context context;

    public void onCreate() {
        super.onCreate();
        ApplicationUtils.context = getApplicationContext();
    }

    public static Context getContext() {
        return ApplicationUtils.context;
    }

    public static String getJavaPackageName() {
        return ApplicationUtils.context.getPackageName();
    }

    public static Resources getAppResources() {
        return getContext().getResources();
    }

    public static String getResouceName(Integer id) {
        return getAppResources().getResourceName(id);
    }

    public static String getResourceString(Integer id) {
        return getAppResources().getString(id);
    }

    public static int getResourceId(String variableName, String resourceName, String packageName) {
        try {
            return getContext().getResources().getIdentifier(variableName, resourceName, packageName);
        } catch (Exception e) {
            e.printStackTrace();
            return -1;
        }
    }
}

Lists:

public class Lists extends AppCompatActivity implements CategoriesFragment.OnHeadlineSelectedListener {

    private String listName;

    public void onItemSelected(String currentCategory) {

        ItemsFragment itemsCallBackFragment = (ItemsFragment) getSupportFragmentManager().findFragmentById(R.id.itemsFragment);

        if (itemsCallBackFragment != null) {
            itemsCallBackFragment.updateArticleView(currentCategory);
        } else {
            ItemsFragment itemFragment = new ItemsFragment();
            FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();

            transaction.replace(R.id.itemsFragment, itemFragment);
            transaction.addToBackStack(null);

            transaction.commit();
        }
    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_triple_list);

        setListsAdapter();
    }

    private void setListsAdapter() {
        ViewPager tripleListViewPager = (ViewPager) findViewById(R.id.listsPager);
        FragmentPagerAdapter tripleListFragmentAdapt = new ListsPagerAdapter(getSupportFragmentManager(), "LIST NAME", ApplicationUtils.getContext());
        tripleListViewPager.setAdapter(tripleListFragmentAdapt);
    }
}

ListsPagerAdapter:

public class ListsPagerAdapter extends FragmentPagerAdapter {

    private static int NUM_TABS = 1;
    private String listName;
    private Context listsContext;

    public ListsPagerAdapter(FragmentManager fm, String newListName, Context listsContext) {
        super(fm);
        setListName(newListName);
        setListsContext(listsContext);
    }

    // Returns total number of pages
    @Override
    public int getCount() {
        return NUM_TABS;
    }

    // Returns the fragment to display for that page
    @Override
    public Fragment getItem(int position) {
        switch (position) {
            case 0:
                return NewItemPagerFragment.newInstance();
            default:
                return null;
        }
    }

    // Returns the page title for the top indicator
    @Override
    public CharSequence getPageTitle(int position) {
        switch (position) {
            case 0:
                return listsContext.getResources().getString(R.string.lists_tab_newItem, getListName());
            default:
                return "$value";
        }
    }

    public String getListName() {
        return listName;
    }

    public void setListName(String listName) {
        this.listName = listName;
    }

    public Context getListsContext() {
        return listsContext;
    }

    public void setListsContext(Context listsContext) {
        this.listsContext = listsContext;
    }
}

CategoriesFragment:

public class CategoriesFragment extends Fragment {


    private List<Integer> categoryIds = new ArrayList<>();
    private String currentCategory;
    private View categoriesView;

    OnHeadlineSelectedListener itemCallback;

    public interface OnHeadlineSelectedListener {
        void onItemSelected(String categoryName);
    }

    @Override
    public void onAttach(Context context) {
        super.onAttach(context);
        try {
            itemCallback = (OnHeadlineSelectedListener) context;
        } catch (ClassCastException e) {
            throw new ClassCastException(context.toString()
                    + " must implement OnHeadlineSelectedListener");
        }
    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        categoriesView = inflater.inflate(R.layout.fragment_categories, container, false);
        categoriesView.setId(R.id.categoriesFragment);
        return categoriesView;
    }

    @Override
    public void onActivityCreated(Bundle savedInstanceState) {
        super.onActivityCreated(savedInstanceState);
        setCategoriesListener();
        setDefaultSelectedCategory();
    }

    private void setCategoriesListener() {
        categoryIds.add(R.id.category1);

        for (Integer id : categoryIds) {
            setListener(id);
        }
    }

    private void setListener(final int categoryId) {
        final ImageView categoryImg = (ImageView) categoriesView.findViewById(categoryId);

        categoryImg.setOnClickListener(new View.OnClickListener() {
            public void onClick(View v) {
                removePreviousSelectedCategory(categoryImg);
                selectCategory(categoryId, categoryImg);
                loadItemList();
            }
        });
    }

    private void removePreviousSelectedCategory(ImageView categoryImg) {
        for (Integer id : categoryIds) {
            final ImageView imgView = (ImageView) categoriesView.findViewById(id);

            if (imgView.getTag() != null) {
                String previousSelectedCategory = (String) imgView.getTag();
                if (StringUtils.contains(previousSelectedCategory, Application.SELECTED)) {
                    previousSelectedCategory = previousSelectedCategory.replace(Application.SELECTED, "");
                    categoryImg.setTag(previousSelectedCategory);

                    Integer previousSelectedCategoryId = ApplicationUtils.getResourceId(previousSelectedCategory, Application.RES_DRAWABLE, ApplicationUtils.getJavaPackageName());
                    imgView.setImageResource(previousSelectedCategoryId);
                }
            }
        }
    }

    private void selectCategory(int categoryId, ImageView categoryImg) {
        String newSelectedCategory = ApplicationUtils.getResouceName(categoryId) + Application.SELECTED;
        setCurrentCategory(newSelectedCategory);

        newSelectedCategory = newSelectedCategory.replace(Application.META_INFO_ID, "");
        categoryImg.setTag(newSelectedCategory);

        Integer currentCategoryId = ApplicationUtils.getResourceId(newSelectedCategory, Application.RES_DRAWABLE, ApplicationUtils.getJavaPackageName());
        categoryImg.setImageResource(currentCategoryId);
    }

    public String getCurrentCategory() {
        return currentCategory;
    }

    public void setCurrentCategory(String currentCategory) {
        this.currentCategory = currentCategory;
    }

    private void loadItemList() {
        itemCallback.onItemSelected(getCurrentCategory());
    }

    private void setDefaultSelectedCategory() {
        Integer categoryId = R.id.category1;
        final ImageView categoryImg = (ImageView) categoriesView.findViewById(categoryId);
        selectCategory(categoryId, categoryImg);
        loadItemList();
    }
}

ItemsFragment:

public class ItemsFragment extends Fragment {

    private View itemsFragmentView;
    private ListView itemsListView;
    private ItemAdapter itemsListAdapter;

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        itemsFragmentView = inflater.inflate(R.layout.fragment_items, container, false);
        itemsListView = (ListView) itemsFragmentView.findViewById(R.id.itemsListView);

        setItemsListAdapter();
        return itemsFragmentView;
    }

    public void updateArticleView(String categoryName) {
        getSelectedCategoryList();
    }

    private void getSelectedCategoryList() {
        List<String> testList = new ArrayList<>();

        testList.add("TEST ITEM");
        itemsListAdapter.update(testList);
    }

    private void setItemsListAdapter() {
        itemsListAdapter = new ItemAdapter(getActivity(), R.layout.fragment_items, new ArrayList<String>());
        itemsListView.setAdapter(itemsListAdapter);
    }
}

ItemAdapter:

public class ItemAdapter extends ArrayAdapter<String> {

    private List<String> items = new ArrayList<>();
    private LayoutInflater rowInflater;

    public ItemAdapter(Context context, int resource, List<String> itemsList) {
        super(context, resource, itemsList);
        this.items = itemsList;
        this.rowInflater = LayoutInflater.from(context);
    }

    private class ItemHolder {
        TextView itemNameView;
    }

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

        ItemHolder itemHolder;

        if (rowView == null) {
            rowView = rowInflater.inflate(R.layout.content_row_choose_item, parent, false);

            itemHolder = new ItemHolder();
            itemHolder.itemNameView = (TextView) rowView.findViewById(R.id.itemName);

            rowView.setTag(itemHolder);
        } else {
            itemHolder = (ItemHolder) rowView.getTag();
        }

        String itemName = getItem(position);
        if (StringUtils.isNotEmpty(itemName) && itemHolder.itemNameView != null) {
            itemHolder.itemNameView.setText(itemName);
            System.out.println("ITEM NAME ### " + itemHolder.itemNameView.getText());
        }

        return rowView;
    }

    public void update(List<String> items) {
        this.items.clear();
        this.items.addAll(items);
        this.notifyDataSetChanged();
    }


    public List<String> getItems() {
        return items;
    }
}
Community
  • 1
  • 1
Victor Oliveira
  • 3,293
  • 7
  • 47
  • 77
  • Dont do this.. `this.items = items;`. Instead, try `this.items.clear(); this.items.addAll(items)` – OneCricketeer Oct 13 '16 at 19:44
  • @cricket_007 I've tried to do this, but since ItemList is referenced on Adapter's instantiation, if I clear the list once, then everything goes away. – Victor Oliveira Oct 13 '16 at 19:50
  • @cricket_007 based on what u've said I came with the idea to create a local list inside `getSelectedCategory` and pass to Adapter, still doesn't work... – Victor Oliveira Oct 13 '16 at 19:54
  • 1
    And the same thing will happen if you set `this.items = items`. My point is that you should not de-reference the arraylist – OneCricketeer Oct 13 '16 at 19:54
  • haven't read the entire code, but found one bug, don't know if it's related to your issues or not: you can't call getActivity() from a fragment's onCreateView as the fragment is not guaranteed to be attached yet, thus getActivity might be null – marmor Oct 13 '16 at 19:58
  • Anyways, your post has really evolved, and it is hard to follow where the problem exists. Is there a way to make this a [mcve]? You seem to want to use the ViewHolder pattern, but then got rid of it. There are many method calls to simple or one-line methods. Not clear what `ApplicationUtils` is doing, but your Fragment should use `getActivity()` for a Context – OneCricketeer Oct 13 '16 at 19:58
  • @marmor if it was null, I guess Fragment wouldn't be rendered with a single Item on Activity first load, as it's right now – Victor Oliveira Oct 13 '16 at 20:00
  • @cricket_007 `ApplicationUtils` extends `Application` and inside has a static method just calling `getContext`. I can't make the MCV again right now because it's late night here, but tomorrow I will post it so you can take a look. Thanks for supporting me – Victor Oliveira Oct 13 '16 at 20:05
  • Please **do not** post links to code projects. If at all possible, rewrite the entire question with the relevant, minimal code. – OneCricketeer Oct 15 '16 at 21:51
  • Copy - I will take a little longer than, but I thought that would be better instead of posting 10 files directly here – Victor Oliveira Oct 15 '16 at 21:56

2 Answers2

0

Random stab...

If using a ListFragment, then fragment_items.xml needs a ListView with android:id="@android:id/list".

You are even trying to load the ListView with that id.

itemsFragmentView = inflater.inflate(R.layout.fragment_items, container, false);
itemsListView = (ListView) itemsFragmentView.findViewById(android.R.id.list);

However, you have android:id="@+id/itemsListView"

fragment_items:

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

    <ListView
        android:id="@+id/itemsListView"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_centerHorizontal="true"
        android:layout_centerVertical="true"
        android:divider="@null" />
</LinearLayout>

If using a ListFragment, you do not need to implement your own XML layout file. That being said, onCreateView doesn't need implemented. Use onActivityCreated instead.

@Override
public void onActivityCreated(Bundle savedInstanceState) {
    itemsListView = getListView();
    itemsListAdapter = new ItemAdapter(getActivity());
    setListAdapter(itemsListAdapter);
    super.onActivityCreated(savedInstanceState);
}

Now, if you want to add items to the adapter, I generally recommend you just use an ArrayAdapter<String>. This will provide you an add(String object) method that will both add to the underlying list and notify for updates.

Regarding the getView problems, can't really say. I don't really know why you need ItemAdapter since the default ArrayAdapter<String> will work assuming you pass it a layout with android:id="@android:id/text1, for example using android.R.layout.simple_list_item_1 in place of R.layout.content_row_choose_item

So, that would look something like.

@Override
public void onActivityCreated(Bundle savedInstanceState) {
    itemsListView = getListView();
    ArrayAdapter<String> itemsListAdapter = new ArrayAdapter<String>(
          getActivity(), android.R.layout.simple_list_item_1);
    setListAdapter(itemsListAdapter);

    adapter.add("Hello");
    adapter.add("World");

    super.onActivityCreated(savedInstanceState);
}
OneCricketeer
  • 179,855
  • 19
  • 132
  • 245
  • "You are even trying to load the ListView with that id" - I need to provide another MCV - in my code this is already implemented. I'm answering from my cel, so shortly replying a gave up on ArrayAdapter because I needed a custom background to my ListView - everything was working until I changed from ArrayAdapter to BaseAdapter. I will take a better look on your answer tomorrow and again, thanks for ur support - after 7 days with this problem and no answers here I was almost giving up – Victor Oliveira Oct 13 '16 at 20:22
  • ArrayAdapter will allow you define a custom background... Perhaps you should have left the code in the working state, then asked about that? – OneCricketeer Oct 13 '16 at 20:24
  • Hey man, sorry for my delay on posting this MCVE - I guess you now have everything to reproduce the problem. I must say I thought I had committed the last working state of my code, but unfortunately I haven't, which means now I must fix it. I made a short mistake saying that I needed this for a custom row layout. Actually I need a Custom Adapter. For future purposes I might use Image Views and other components. The simple layout provided by Android won't help me to customize some stuff that I need right now, for example `android:divider="@null"`, at least as far as I tested by me... – Victor Oliveira Oct 15 '16 at 21:55
  • Sorry, but I'm not going to download your project just to search for the error. – OneCricketeer Oct 15 '16 at 22:01
  • I'm rewriting the question with the MCVE that u asked, if it helps - If u still don't want to go further, then thanks for the time u took so far to support me and other programmers that might go through this error – Victor Oliveira Oct 15 '16 at 22:04
0

The given MCVE was working fine. After 7 days debugging and uncommenting code I found the answer to my problem.

My list was correctly being updated, but wasn't being rendered because of a second ItemsFragemnt being initialized on Lists Activity load. This second fragment was overlaying the first one and that's why I wasn't seing my list being displayed/refreshed.

Lists:

First created fragment:

private void setListsAdapter() {
                ViewPager tripleListViewPager = (ViewPager) findViewById(R.id.listsPager);
                FragmentPagerAdapter tripleListFragmentAdapt = new ListsPagerAdapter(getSupportFragmentManager(), "LIST NAME", ApplicationUtils.getContext());
                tripleListViewPager.setAdapter(tripleListFragmentAdapt);       }

Inside ListsPagerAdapter there was a call to populate my ViewPager (NewItemPagerFragment) with CategoriesFragment and ItemsFragment.

Second created fragment - Overlay:

        ItemsFragment itemFragment = new ItemsFragment();
        FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();

        transaction.replace(R.id.itemsFragment, itemFragment);
        transaction.addToBackStack(null);

        transaction.commit();

I tried to breakpoint my code, mentioned before in Facts at question scope, but sometimes this is really painful. I came with the idea to use Log.d(TAG_KEY, TAG_NAME); and that was when I found out the answer: I realized that ItemsFragment was being called twice on Activity load.

I searched for new ItemsFragment in my project and saw that inside my NewItemPagerFragment onCreate, ItemsFragment was being called. Also before CategoriesFragment- just like this:

NewItemPagerFragment:

    Fragment itemsFragment = new ItemsFragment();
    FragmentManager fm1 = getChildFragmentManager();
    FragmentTransaction ft1 = fm1.beginTransaction();
    ft1.replace(R.id.itemsFragment, itemsFragment);
    ft1.commit();


    Fragment categoriesFragment = new CategoriesFragment();
    FragmentManager fm = getChildFragmentManager();
    FragmentTransaction ft = fm.beginTransaction();
    ft.replace(R.id.categoriesFragment, categoriesFragment);
    ft.commit();

which explains Facts 2.. After commenting/remove this call on NewItemPagerFragment the problem was gone.

Bottom line is: Make sure your list is correctly being fulfilled/updated with things like notifyDataSetChanged as cricket_007 mentioned in his answer and other thousand posts I read on internet. Not less, make sure you are not overlaying the fragment calling its initialization twice.

Victor Oliveira
  • 3,293
  • 7
  • 47
  • 77