Ad

I Can't Figure Out Why My List View Is Not Updated As I Expect

- 1 answer

I have a Fragment containing a RecyclerView for a list of items (groups) from a database. The user can navigate to a separate page to create a new group. In that action the group gets inserted into the database via a repository with asynchronous task. During that, the user is returned to the page holding the list, so the onResume method of the Fragment is called. When the insert record database operation is completed, the Repository posts the new list in the LiveData. The adapter for the group list is updated, but the view is not. I don't know why. I thought I was following the MVVM pattern properly.

Here is logger output, showing that the insert operation completes after onResume. The GroupListAdapter method to update the list is called, as expected. In this case "GroupListAdapter: SetGroups 2" meaning 2 groups, after having 1 before the user creates the 2nd.

HomeFragment onAttach
HomeFragment onCreate
HomeFragment onCreateView
GroupsFragment onAttach
GroupsFragment onCreate
GroupsFragment initData
GroupListViewModel created
GroupsFragment onCreateView
GroupsFragment: Do group list view
GroupsFragment list changed
GroupListAdapter: setGroups null
GroupListAdapter: setGroups inserted
GroupsFragment: Do group list view
GroupsFragment onResume
GroupsFragment: Do group list view
Repository: on group list change.
GroupsFragment list changed
GroupListAdapter: setGroups 1
GroupListAdapter: setGroups inserted
GroupsFragment: Do group list view
GroupsFragment list changed
GroupListAdapter: setGroups 0
GroupListAdapter dispatch updates

GroupListAdapter: setGroups 1 <<<<<<<<<< Initially 1 item in the group
GroupListAdapter dispatch updates
GroupsFragment: Do group list view
Finish create group activity. Result: Intent { (has extras) }
MainActivity onActivityResult for request 6, result: -1
Handle result of create group activity.
GroupListViewModel created
GroupListViewModel insert group
Created insert group task.
Async insert group.
Group inserted. <<<<<<<<<<<<<<<<<<<<<<< insertion completes before fragment's onResume
GroupsFragment onResume
GroupsFragment: Do group list view
Repository: on group list change.
GroupsFragment list changed
GroupListAdapter: setGroups 2 <<<<<<<<<<<<< The adapter knows there are 2 items in the list
GroupListAdapter dispatch updates
GroupsFragment: Do group list view  >>>>>>>>>>>>>>>> Fragment should update, but does not.

Following are classes involves. The Fragment:

public class GroupsFragment extends Fragment
{
private Context m_context = null;
private RecyclerView rv_groups;
private GroupListAdapter adapter = null;
private TextView tv_noGroups;

private GroupListViewModel m_groupViewModel;

public GroupsFragment ()
{} // Required empty public constructor

@Override
public void onAttach (Context context)
{
    Logger.get().fine("GroupsFragment onAttach");
    super.onAttach(context);
    m_context = context;
}

@Override
public void onCreate (@Nullable Bundle savedInstanceState)
{
    Logger.get().fine("GroupsFragment onCreate");
    super.onCreate(savedInstanceState);
    initData();
}

private void initData ()
{
    Logger.get().fine("GroupsFragment initData");
    m_groupViewModel = ViewModelProviders.of(this).get(GroupListViewModel.class);
    if (m_groupViewModel.getAllGroups() == null)
        Logger.get().severe("null group list live data in viewmodel");
    m_groupViewModel.getAllGroups().observe(this, groups -> {
        Logger.get().fine("GroupsFragment list changed");
        adapter.setGroups(groups); // FIXME: Shouldn't this cause view update?
        doListView(); // FIXME: Shouldn't need this?
    });
}

@Override
public View onCreateView (@NonNull LayoutInflater inflater, ViewGroup container,
                          Bundle savedInstanceState)
{
    Logger.get().fine("GroupsFragment onCreateView");
    View view = inflater.inflate (R.layout.fragment_groups, container, false);

    adapter = new GroupListAdapter(m_context);

    initListView(view);
    FloatingActionButton fab_add;
    fab_add = view.findViewById(R.id.fab_add);
    fab_add.setOnClickListener (v -> {
        // FIXME: getActivity may return null if fragment is associated with Context, not Activity
        getActivity().startActivityForResult(new Intent(getContext(), CreateGroupActivity.class), Activities.CREATE_GROUP);
    });
    setHasOptionsMenu (true);
    return view;
}

protected void initListView (View view)
{
    rv_groups = view.findViewById(R.id.rv_groups);
    rv_groups.setAdapter(adapter);
    tv_noGroups = view.findViewById(R.id.tv_noGroups);
    RecyclerView.LayoutManager mLayoutManager = new LinearLayoutManager(m_context);
    rv_groups.setLayoutManager(mLayoutManager);
    rv_groups.setItemAnimator(new DefaultItemAnimator());
    rv_groups.addItemDecoration(new DividerItemDecoration(m_context, DividerItemDecoration.VERTICAL));
    doListView();
}

/** Indicate on the UI that there are no groups to display. */
private void showNoGroups ()
{
    rv_groups.setVisibility(View.GONE);
    tv_noGroups.setVisibility(View.VISIBLE);
    tv_noGroups.setText(getResources().getString(R.string.string_noGroups));
}

private void doListView ()
{
    Logger.get().fine("GroupsFragment: Do group list view");
    if (m_groupViewModel == null || m_groupViewModel.countGroups() == 0)
    {
        showNoGroups();
    }
    else
    {
        rv_groups.setVisibility(View.VISIBLE);
        tv_noGroups.setVisibility(View.GONE);
    }
}

@Override
public void onResume ()
{
    Logger.get().fine("GroupsFragment onResume");
    super.onResume();
    doListView();
}
}

The ViewModel:

public class GroupListViewModel extends AndroidViewModel
{
private final Repository m_repository;
private final MediatorLiveData<List<GroupEntity>> m_groups;

public GroupListViewModel (@NonNull Application application)
{
    super(application);
    m_groups = new MediatorLiveData<>();
    // set null until we get data from the database.
    m_groups.setValue(null);
    m_repository = ((MyApp)application).getRepository();
    LiveData<List<GroupEntity>> groups = m_repository.getAllGroups();
    // observe the changes of the groups from the database and forward them
    m_groups.addSource(groups, m_groups::setValue);
    Logger.get().finer("GroupListViewModel created");
}

public int countGroups ()
{
    if (m_groups.getValue() == null)
        return 0;
    return m_groups.getValue().size();
}

public LiveData<List<GroupEntity>> getAllGroups ()
{ return m_groups; }

public List<GroupEntity> searchGroups (String query)
{ return m_repository.searchGroups(query); }

public void insert (GroupEntity group)
{
    Logger.get().finer("GroupListViewModel insert group");
    m_repository.insertGroup(group);
}

public void update (GroupEntity group)
{
    Logger.get().finer("GroupListViewModel update group");
    m_repository.updateGroup(group);
}
}

The Adapter:

public class GroupListAdapter extends RecyclerView.Adapter<GroupListAdapter.GroupViewHolder>
{
private List<? extends Group> m_groupList;

private Context context;

public GroupListAdapter (Context context)
{
    this.context = context;
}

class GroupViewHolder extends RecyclerView.ViewHolder
{
    TextView name;
    LinearLayout lay_group;

    GroupViewHolder (View itemView)
    {
        super(itemView);
        name = itemView.findViewById(R.id.tv_name);
        lay_group = itemView.findViewById(R.id.lay_groups);
    }
}

@Override
@NonNull
public GroupViewHolder onCreateViewHolder (@NonNull ViewGroup parent, int viewType)
{
    View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.layout_groups, parent, false);
    return new GroupViewHolder(view);
}

@Override
public void onBindViewHolder (@NonNull final GroupViewHolder holder, int position)
{
    final Group group = m_groupList.get(position);
    holder.name.setText(group.getName());
    // Set listener to show group details when group in list is clicked
    holder.lay_group.setOnClickListener(v -> {
        Intent intent = new Intent(context, GroupDetailActivity.class);
        Logger.get().fine("Start group details activity for id " + group.getId());
        intent.putExtra(GroupEntity.GROUP_ID_KEY, group.getId());
        context.startActivity(intent);
    });
}

public void setGroups (@Nullable final List<? extends Group> groups)
{
    Logger.get().fine("GroupListAdapter: setGroups "  + (groups == null ? "null" : groups.size()));
    if (m_groupList == null)
    {
        m_groupList = groups;
        notifyItemRangeInserted(0, groups == null ? 0 : groups.size());
        Logger.get().fine("GroupListAdapter: setGroups inserted");
        return;
    }
    DiffUtil.DiffResult result = DiffUtil.calculateDiff(new DiffUtil.Callback()
    {
        @Override
        public int getOldListSize ()
        { return m_groupList.size(); }

        @Override
        public int getNewListSize ()
        { return groups.size(); }

        @Override
        public boolean areItemsTheSame (int oldItemPosition, int newItemPosition)
        { return m_groupList.get(oldItemPosition).getId() == groups.get(newItemPosition).getId(); }

        @Override
        public boolean areContentsTheSame (int oldItemPosition, int newItemPosition)
        {
            Group newGroup = groups.get(newItemPosition);
            Group oldGroup = m_groupList.get(oldItemPosition);
            return newGroup.getId() == oldGroup.getId()
                    && (CommonUtils.equalStrings(newGroup.getName(), oldGroup.getName()));
        }
    });
    m_groupList = groups;
    Logger.get().finer("GroupListAdapter dispatch updates");
    result.dispatchUpdatesTo(this);
}

@Override
public int getItemCount()
{
    // Must allow for groups not completed loading yet
    if (m_groupList == null)
        return 0;
    return m_groupList.size();
}

@Override
public long getItemId (int position)
{ return m_groupList.get(position).getId(); } // Note online BasicSample example does not check for null list here.
}

Repository:

public class Repository
{
private static Repository sInstance;

private final Database m_database;

private GroupDAO m_groupDAO;

private MediatorLiveData<List<GroupEntity>> m_observableGroups;
//private LiveData<List<GroupEntity>> m_groups;

public static Repository getInstance (final Database database)
{
    if (sInstance == null)
    {
        synchronized (Repository.class)
        {
            if (sInstance == null)
                sInstance = new Repository(database);
        }
    }
    return sInstance;
}

private Repository (final Database database)
{
    m_database = database;
    load();
}

public Repository (Application application)
{
    this(Database.getDatabase(application));
}

/**
 * Get all data access objects.
 */
private void getDAO ()
{
    m_groupDAO = m_database.groupDAO();
}

/**
 * Get objects from the database, to store in this repository.
 */
private void load ()
{
    getDAO();
    Logger.get().info("Load objects to repository");
    m_observableGroups = new MediatorLiveData<>();
    m_observableGroups.addSource(m_database.groupDAO().loadAllSync(),
            new Observer<List<GroupEntity>>()
            {
                @Override
                public void onChanged (List<GroupEntity> groupEntities)
                {
                    Logger.get().info("Repository: on group list change.");
                    if (m_database.getDatabaseCreated().getValue() != null)
                        m_observableGroups.postValue(groupEntities);
                }
            }
    );
}

public LiveData<List<GroupEntity>> getAllGroups ()
{ return m_observableGroups; }

/*private LiveData<GroupEntity> loadGroup (final int id)
{ return m_database.groupDAO().loadById(id); }*/

// TODO which getGroup? return LiveData<GroupEntity> ?
/*public GroupEntity getGroup (int id)
{
    Logger.get().finer("Repository: getGroup.");
    for (GroupEntity group : getAllGroups().getValue())
    {
        if (group.getId() == id)
            return group;
    }
    return null;
}*/

public GroupEntity getGroup (int id)
{
    Logger.get().finer("Repository: getGroup.");
    return m_database.groupDAO().loadById(id);
}

public LiveData<GroupEntity> getGroupSync (int id)
{
    Logger.get().finer("Repository: getGroup.");
    return m_database.groupDAO().loadByIdSync(id);
}

public void insertGroup (GroupEntity group)
{ new insertGroupTask(m_groupDAO).execute(group); }

private static class insertGroupTask extends AsyncTask<GroupEntity, Void, Void>
{
    private GroupDAO mAsyncTaskDao;

    insertGroupTask(GroupDAO dao) {
        Logger.get().info("Created insert group task.");
        mAsyncTaskDao = dao;
    }

    @Override
    protected Void doInBackground (final GroupEntity... params)
    {
        Logger.get().info("Async insert group.");
        mAsyncTaskDao.insert(params[0]);
        Logger.get().fine("Group inserted.");
        return null;
    }
}
}

Group:

public interface Group
{
    ...
}

Group Entity

@Entity(tableName = "groups")
public class GroupEntity implements Group
{
    ...
}
Ad

Answer

No mistake in the java. A GUI newbie mistake in the layout definition meant the groups would be displayed one per page. Changed TextView layout_height to "wrap_content".

Ad
source: stackoverflow.com
Ad