0

I followed this google tutorial and implemented a dynamic ListView for drag and drop https://www.youtube.com/watch?v=_BZIvjMgH-Q

listView = (DynamicListView) findViewById(R.id.listview);
adapter = new StableArrayAdapter(this, R.layout.text_view, arraylist);
listView.setAdapter(adapter);
listView.setChoiceMode(ListView.CHOICE_MODE_SINGLE);

// added the items later and called notifyDataSetChanged();
    arraylist.add("item 1");
    arraylist.add("item 2");
    arraylist.add("item 3");
    arraylist.add("item 4");

adapter.notifyDataSetChanged();

It shows up all the items in the listview but when I drag and drop the elements they disappear.

If I add the elements before setting the adapter listView.setAdapter(adapter); it works totally fine.

Library Source code http://developer.android.com/shareables/devbytes/ListViewDraggingAnimation.zip

How do i fix this?

UPDATE

StableArrayAdapter.java

package com.example.android.navigationdrawerexample;

import android.content.Context;
import android.widget.ArrayAdapter;

import java.util.HashMap;
import java.util.List;

public class StableArrayAdapter extends ArrayAdapter<String> {

    final int INVALID_ID = -1;

    int mRowLayout;
    List<String[]> mStrings;
    HashMap<String, Integer> mIdMap = new HashMap<String, Integer>();

    // constructor
    public StableArrayAdapter(Context context, int rowLayout, List<String> objects) {
        super(context, rowLayout, objects);
        for (int i = 0; i < objects.size(); ++i) {
            mIdMap.put(objects.get(i), i);
        }
    }

    @Override
    public long getItemId(int position) {
        if (position < 0 || position >= mIdMap.size()) {
            return INVALID_ID;
        }
        String item = getItem(position);
        return mIdMap.get(item);
    }

    @Override
    public boolean hasStableIds() {
        return true;
    }
}

UPDATE 2 I added the items to objects here as suggested and it works fine for now but isn't it the same as adding the items in MainActivity before setting the adapter?

public StableArrayAdapter(Context context, int rowLayout, List<String> objects) {
    super(context, rowLayout, objects);

    objects.add("item 1");
    objects.add("item 2");
    objects.add("item 3");
    objects.add("item 4");
    objects.add("item 5");

    for (int i = 0; i < objects.size(); ++i) {
        mIdMap.put(objects.get(i), i);
    }
}

UPDATE 3 The size of the objects is known only in the constructor and mIdMap.put("item 6", objects.size()); in the code has no effect.

 // constructor
    public StableArrayAdapter(Context context, int rowLayout, List<String> objects) {
        super(context, rowLayout, objects);

        this.objects = objects;

        objects.add("item 1");
        objects.add("item 2");
        objects.add("item 3");
        objects.add("item 4");
        objects.add("item 5");

        for (int i = 0; i < objects.size(); ++i) {
            mIdMap.put(objects.get(i), i);
        }

        mIdMap.put("item 6", objects.size());    // has no effect

    }

    public void addItem(String item) {
        int index = objects.size();
        mIdMap.put(item, index);
    }
Vinay Potluri
  • 545
  • 1
  • 9
  • 23
  • 1
    Please post code for StableArrayAdapter. And add another tag of "drag-and-drop". In my opinion, this post does not apply to "android-animation". – The Original Android Jul 09 '15 at 06:28
  • Did you solve this problem yet? If not, you may respond to my posted answer for your status on this. – The Original Android Jul 09 '15 at 21:23
  • 1
    Vinay, Next time, post your comment in my answer so that I will notice it. I almost overlooked your question in UPDATE 2. Or add your question in your post and as a comment to my posted answer. Anyway.... – The Original Android Jul 11 '15 at 02:18
  • 1
    Vinay, The answer is that it is NOT the same as adding the items in MainActivity AFTER setting the adapter. Your original code added more strings AFTER setting the adapter. The reason is object mIdMap has a different memory address than object arraylist. This is important knowledge in Java, good luck. – The Original Android Jul 11 '15 at 02:20
  • 1
    Vinay, I added comments/code under the UPDATE section in my posted answer. – The Original Android Jul 17 '15 at 17:22
  • I think this is the solution for your problem: [link](http://stackoverflow.com/a/27153768/1233979) – huu duy Dec 06 '15 at 18:15

3 Answers3

1

I normally add items onto the ArrayList or modify the object in the Adapter code (StableArrayAdapter in your case), and not outside the adapter as what you did. But that might be fine as long as the object outside the ArrayAdapter shares the same address as the list in StableArrayAdapter. I suspect it is not.

In your case, make sure that code like arraylist.add("item 1") adds the strings onto the ArrayList object in StableArrayAdapter. Again, I suspect it is not.

To speed things up, please post the code for StableArrayAdapter. It's not convenient for the readers to help if they must download the code.

NEW suggested code:

In StableArrayAdapter, add a public method like:

// This method is opposite of getItemId().
public void addItem(String item) {
    // Add the string into the objects
    objects.add(item);

    // size of objects should be added by one after add() above
    int index = objects.size();
    mIdMap.put(item, index);
}

Outside StableArrayAdapter:

listView.setAdapter(adapter);
listView.setChoiceMode(ListView.CHOICE_MODE_SINGLE);

// added the item and called notifyDataSetChanged();
adapter.addItem("item 1");
adapter.addItem("item 2");

adapter.notifyDataSetChanged();

Note: Instead of adding items onto arraylist, call a method of StableArrayAdapter, object adapter.

UPDATE 1: The problem is that the methods setCheeseList() and swapElements() in DynamicListView class does NOT work for adding items dynamically. Also addItem() in StableArrayAdapter is also changed. I fixed this already in my project but it is not easy to explain. So I will try.

Code Suggestions:

Part 1, FROM:

private void handleCellSwitch() {
...
   swapElements(mCheeseList, originalItem, getPositionForView(switchView));
   ((BaseAdapter) getAdapter()).notifyDataSetChanged();
...

TO:

private void handleCellSwitch() {
...
   // Swap the 2 items with animation for the dragNdrop feature
   final SwappableListAdapter adapter = (SwappableListAdapter) getAdapter();
   adapter.swap(originalItem, getPositionForView(switchView));
...

Notes:

  • Instead of calling the local method swapElements() to swap values between mCheeseList, I call a method swap() of Interface SwappableListAdapter which is implemented in StableArrayAdapter class.
  • Basically the idea is to depend on getting the correct data in StableArrayAdapter.

Part 2 Code for Interface SwappableListAdapter:

public interface SwappableListAdapter {
        /**
         * Swaps between 2 items in the adapter, one is the mobile item, another is the selected item.
         * Method notifyDataSetChanged is called in the swap() when finished.
         * @param index1 The selected row view, dragged and to be moved.
         * @param index2 The row which moved due to the dragged row item.
         */
    public void swap(int index1, int index2);
}

Notes:

  • This is an interface created in DynamicListView class because this is the class that will make the actual call.
  • This will fix compiler error issue.

Part 3 Code for implementing the swap() method in StableArrayAdapter class.

TO:

public class StableArrayAdapter extends ArrayAdapter<String> implements DynamicListView.SwappableListAdapter {
   ...
   @Override
   public void swap(int indexOne, int indexTwo) {
      // Swap mIdMap object related to indexOne and indexTwo
      String item = getItem(indexOne);

      notifyDataSetChanged();
}

Notes:

  • The swap() needs work. My project do not use the HashMap for swapping, and I don't like the current implementation (from Google engineer) of the HashMap. I might have time to fix it. Perhaps you can fix this method. Hopefully you have the idea by now.
  • StableArrayAdapter class now implements the interface SwappableListAdapter, and have access to override the swap().
  • The idea of the interface is that the Adapter is now the central point for storing data! The current implementation stores data both in the Adapter and DynamicListView, and that should not be.
The Original Android
  • 6,147
  • 3
  • 26
  • 31
  • @Vinay, Looking the posted code, now I know that I am correct. One simple solution is to create a public method in StableArrayAdapter that accepts the new string values in arraylist. The method would update object mIdMap. What do you think? Can you do that? – The Original Android Jul 09 '15 at 16:50
  • Is there any other way to add objects in the MainActivity? Because In one of my apps I use Retrofit API to retrieve weather information for 5 different locations. So, I call the API in a for-loop -> add it to the arraylist -> adapter.notifyDataSetChanged(); Elements are added but they dissapear when I dragNdrop. – Vinay Potluri Jul 11 '15 at 02:37
  • @Vinay, I added texts in "NEW suggested code section". I hope it's clear. Most importantly, I hope you understand why your original code does not work. – The Original Android Jul 11 '15 at 03:23
  • i can understand better now but I still cant make it work. It's strange to see when I add the line mIdMap.put("item 6", objects.size()); after the for-loop in UPDATE 2 it doesn't show in the listview. Am I missing something ? – Vinay Potluri Jul 14 '15 at 08:31
  • i added items using a public method http://codeviewer.org/view/code:534b stableArrayAdapter class with the method http://codeviewer.org/view/code:534c – Vinay Potluri Jul 14 '15 at 08:52
  • @Vinay, For my understanding, this means that now you don't see any items in the ListView. Is that right? I am now looking at method setCheeseList(). This is not a normal standard for using the Adapter. Unfortunately the code design is not well made for adding items onto the Adapter dynamically. For my question, please try to respond quickly since it is hard for me or any of us to remember one issue after another. Thanks. – The Original Android Jul 14 '15 at 21:07
  • yes, i don't see any items now. Here is the DynamicListView class http://codeviewer.org/view/code:535c – Vinay Potluri Jul 14 '15 at 23:00
  • 1
    @Vinay, I added comments/code under the UPDATE section. – The Original Android Jul 17 '15 at 00:01
  • I made changes exactly the way you suggested me and it works perfect. For those looking for the exact solution. I will update it as an answer. – Vinay Potluri Jul 18 '15 at 09:27
  • You Sir, are simply awesome. I'd like to buy you coffee sometime if you plan to visit the Bay area. – Vinay Potluri Jul 18 '15 at 09:35
  • @Vinay, I may take up your offer of coffee. I like the Bay area and LA is not too far or remote. Can I have your email address? – The Original Android Jul 21 '15 at 21:17
  • @Vinay, Just curious, is this effort for a project in a company? That would be cool if so. I live in LA and might want to work in the Bay area because I like San Francisco. Thanks for any answer. – The Original Android Jul 21 '15 at 21:19
  • @Vinay, Thanks for sharing that info, that sounds crazy to me! Sent u email. – The Original Android Jul 22 '15 at 00:18
  • Hello @TheOriginalAndroid, What about custom listview using base adapter.... I have checked many questions and blogs, mainly I got the example of DynamicListView with Arrayadapter (Which is here in answers - I guess), I tried to implement baseadapter instead of arrayadapter, but no luck. – Bhuro Aug 12 '16 at 11:41
1

Thanx a ton to the @The Original Android

Summary of the changes made to make it work when the items are added dynamically.

StableArrayAdapter.java

added addItem method

public void addItem(String item) {
    // Add the string into the objects
    objects.add(item);

    // size of objects should be added by one after add() above
    int index = objects.size();
    mIdMap.put(item, index);
}

MainActivity.java

listView.setCheeseList(al);
listView.setAdapter(adapter);
listView.setChoiceMode(ListView.CHOICE_MODE_SINGLE);

// called addItem method inside MainActivity
adapter.addItem("item 1");
adapter.addItem("item 2");
adapter.addItem("item 3");
adapter.addItem("item 4");

adapter.notifyDataSetChanged();

DynamicListView.java

Changed from

    private void handleCellSwitch() {
     ...   
       swapElements(mCheeseList, originalItem, getPositionForView(switchView));
       ((BaseAdapter) getAdapter()).notifyDataSetChanged();
     ...
    }

to

   private void handleCellSwitch() { 
    ...      
      final SwappableListAdapter adapter = (SwappableListAdapter) getAdapter();
      adapter.swap(mCheeseList,originalItem, getPositionForView(switchView)); // I still had to pass the arraylist   
    ...
    }

Created an interface inside DynamicListView.java

public interface SwappableListAdapter {    
    public void swap(int index1, int index2);
}

StableArrayAdapter.java implements the SwappableListAdapter interface and overrides the swap method

public class StableArrayAdapter extends ArrayAdapter<String> implements DynamicListView.SwappableListAdapter {

    @Override
    public void swap(ArrayList a,int index1, int index2) {

        Object temp = a.get(index1);
        a.set(index1, a.get(index2));
        a.set(index2, temp);

        notifyDataSetChanged();
    }
}
Vinay Potluri
  • 545
  • 1
  • 9
  • 23
  • I am glad you solved the problem! I still don't want to depend on mCheeseList list object. mCheeseList is too powerful since even DynamicListView can change contents in that object. I am glad also that you fully tested the code since I could not due to my code structure. But I still want to do further investigation on this and make a better solution (in my opinion). Do you want me to notify you on this? – The Original Android Jul 21 '15 at 19:37
  • Yes please. kindly notify me if you have some findings – Vinay Potluri Jul 21 '15 at 22:54
1

This might not be the exact same problem, but it's similar, and Google led me here. My list view is mostly working, but there's a certain sequence of actions which will make certain items disappear. Clicking on them afterwards causes a NullPointerException.

Here's the steps to reproduce the bug:

  1. Drag an item to the top.
  2. Drag another item up or down.
  3. The item at the top will disappear.
  4. Drag another item up or down.
  5. The top item will reappear.
  6. Behavior continues if you go to step 2

After debugging, I found that my StableArrayAdapter.getView() method was being called twice, only for the blank item at the top of the list. That led me to this question Adapter getView is called multiple times with position 0 which mentioned "giving a dynamic full height".

To fix it, I set the layout_height for my DynamicListView to "wrap_content".

Community
  • 1
  • 1
Patrick
  • 1,227
  • 14
  • 17