Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Android - Realm - Object delete - Object is no longer valid to operate on

Tags:

android

realm

I'm trying to delete an item from a RecyclerView populated from a Realm Database and I'm getting the following error:

java.lang.IllegalStateException: Illegal State: 
Object is no longer valid to operate on. Was it deleted by another thread?

Assumptions I guess that I'm trying the access when it's already deleted, but I don't understand where.

Context: I'm showing a list of cities and longClicking on an item shows a dialog asking to confirm the deletion.

The item is deleted in the database since when I relaunch the app, it's not there anymore.

Realm to ArrayList

public static ArrayList<City> getStoredCities(){
        RealmQuery<City> query = getRealmInstance().where(City.class);
        final RealmResults<City>results =
                realm.where(City.class)
                        .findAllSorted("timestamp", Sort.DESCENDING);

        results.size();

        ArrayList<City> cityArrayList = new ArrayList<>();

        for(int i = 0; i< results.size(); i++){
            cityArrayList.add(results.get(i));
        }

        return cityArrayList;
    }

Dialog code

builder.setPositiveButton(getString(R.string.ok), new DialogInterface.OnClickListener() {
    @Override
    public void onClick(DialogInterface dialogInterface, int i) {
        RealmHelper.removeCity(cityArrayList.get(position));
        cityArrayList.remove(position);
        mRecyclerView.removeViewAt(position);
        mCityListAdapter.notifyItemRemoved(position);
        mCityListAdapter.notifyItemRangeChanged(position, cityArrayList.size());
        mCityListAdapter.notifyDataSetChanged();
    }
});

Realm method to delete the item

public static void removeCity(City city){
        RealmResults<City> result = realm.where(City.class).equalTo("cityName", city.getCityName()).findAll();
        realm.beginTransaction();
        result.deleteAllFromRealm();
        realm.commitTransaction();
}

Logs

07-28 11:02:08.461 9461-9461/com.ilepez.weatherapp E/AndroidRuntime: FATAL EXCEPTION: main
Process: com.ilepez.weatherapp, PID: 9461
java.lang.IllegalStateException: Illegal State: 
Object is no longer valid to operate on. Was it deleted by another thread?
at io.realm.internal.UncheckedRow.nativeGetString(Native Method)
at io.realm.internal.UncheckedRow.getString(UncheckedRow.java:153)
at io.realm.CityRealmProxy.realmGet$cityName(CityRealmProxy.java:75)
at com.ilepez.weatherapp.data.model.City.getCityName(City.java:41)
at com.ilepez.weatherapp.adapter.CityListAdapter.onBindViewHolder(CityListAdapter.java:56)
at com.ilepez.weatherapp.adapter.CityListAdapter.onBindViewHolder(CityListAdapter.java:20)
at android.support.v7.widget.RecyclerView$Adapter.onBindViewHolder(RecyclerView.java:5768)
at android.support.v7.widget.RecyclerView$Adapter.bindViewHolder(RecyclerView.java:5801)
at android.support.v7.widget.RecyclerView$Recycler.getViewForPosition(RecyclerView.java:5037)
at android.support.v7.widget.RecyclerView$Recycler.getViewForPosition(RecyclerView.java:4913)
at android.support.v7.widget.LinearLayoutManager$LayoutState.next(LinearLayoutManager.java:2029)
at android.support.v7.widget.LinearLayoutManager.layoutChunk(LinearLayoutManager.java:1414)
at android.support.v7.widget.LinearLayoutManager.fill(LinearLayoutManager.java:1377)
at android.support.v7.widget.LinearLayoutManager.onLayoutChildren(LinearLayoutManager.java:588)
at android.support.v7.widget.RecyclerView.dispatchLayoutStep2(RecyclerView.java:3260)
at android.support.v7.widget.RecyclerView.onMeasure(RecyclerView.java:2788)
at android.view.View.measure(View.java:20151)
at android.view.ViewGroup.measureChildWithMargins(ViewGroup.java:6328)
at android.widget.FrameLayout.onMeasure(FrameLayout.java:194)
at android.support.design.widget.NavigationView.onMeasure(NavigationView.java:218)
at android.view.View.measure(View.java:20151)
at android.support.v4.widget.DrawerLayout.onMeasure(DrawerLayout.java:1108)
at android.view.View.measure(View.java:20151)
at android.view.ViewGroup.measureChildWithMargins(ViewGroup.java:6328)
at android.widget.FrameLayout.onMeasure(FrameLayout.java:194)
at android.support.v7.widget.ContentFrameLayout.onMeasure(ContentFrameLayout.java:135)
at android.view.View.measure(View.java:20151)
at android.view.ViewGroup.measureChildWithMargins(ViewGroup.java:6328)
at android.widget.LinearLayout.measureChildBeforeLayout(LinearLayout.java:1464)
at android.widget.LinearLayout.measureVertical(LinearLayout.java:747)
at android.widget.LinearLayout.onMeasure(LinearLayout.java:629)
at android.view.View.measure(View.java:20151)
at android.view.ViewGroup.measureChildWithMargins(ViewGroup.java:6328)
at android.widget.FrameLayout.onMeasure(FrameLayout.java:194)
at android.view.View.measure(View.java:20151)
at android.view.ViewGroup.measureChildWithMargins(ViewGroup.java:6328)
at android.widget.LinearLayout.measureChildBeforeLayout(LinearLayout.java:1464)
at android.widget.LinearLayout.measureVertical(LinearLayout.java:747)
at android.widget.LinearLayout.onMeasure(LinearLayout.java:629)
at android.view.View.measure(View.java:20151)
at android.view.ViewGroup.measureChildWithMargins(ViewGroup.java:6328)
at android.widget.FrameLayout.onMeasure(FrameLayout.java:194)
at com.android.internal.policy.PhoneWindow$DecorView.onMeasure(PhoneWindow.java:3158)
at android.view.View.measure(View.java:20151)
at android.view.ViewRootImpl.performMeasure(ViewRootImpl.java:2594)
at android.view.ViewRootImpl.measureHierarchy(ViewRootImpl.java:1549)
at android.view.ViewRootImpl.performTraversals(ViewRootImpl.java:1841)
at android.view.ViewRootImpl.doTraversal(ViewRootImpl.java:1437)
at android.view.ViewRootImpl$TraversalRunnable.run(ViewRootImpl.java:7403)
at android.view.Choreographer$CallbackRecord.run(Choreographer.java:920)
at android.view.Choreographer.doCallbacks(Choreographer.java:695)
at android.view.Choreographer.doFrame(Choreographer.java:631)
at android.view.Choreographer$FrameDisplayEventReceiver.run(Choreographer.java:906)
at android.os.Handler.handleCall

Adapter code

public class CityListAdapter extends RecyclerView.Adapter<CityListAdapter.CityListViewholder>{

public interface OnItemClickListener{
    void onItemClick(int position);
}

public interface OnItemLongClickListener{
    void onItemLongClick(int position);
}

private static final String LOG_TAG = CityListAdapter.class.getSimpleName();

private ArrayList<City> cityArrayList = new ArrayList<>();
private Context mContext;
private OnItemClickListener onItemClickListener;
private OnItemLongClickListener onItemLongClickListener;

public CityListAdapter(Context context, ArrayList<City> cityArrayList, OnItemClickListener onItemClickListener, OnItemLongClickListener onItemLongClickListener) {
    this.cityArrayList = cityArrayList;
    this.mContext = context;
    this.onItemClickListener = onItemClickListener;
    this.onItemLongClickListener = onItemLongClickListener;
}

@Override
public CityListViewholder onCreateViewHolder(ViewGroup parent, int viewType) {
    View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.city_item_navigation_viewholder, null);
    CityListViewholder cityListViewholder = new CityListViewholder(view, parent.getContext());
    return cityListViewholder;
}

@Override
public void onBindViewHolder(CityListViewholder holder, int position) {
    holder.cityName.setText(cityArrayList.get(position).getCityName());
    holder.bindClick(position, onItemClickListener);
    holder.bindLongClick(position, onItemLongClickListener);
}

@Override
public int getItemCount() {
    return cityArrayList.size();
}


public class CityListViewholder extends RecyclerView.ViewHolder{

    TextView cityName;
    ImageView cityIcon;

    public CityListViewholder(View itemView, Context context) {
        super(itemView);
        cityName = (TextView)itemView.findViewById(R.id.city_name);
        cityIcon = (ImageView)itemView.findViewById(R.id.city_icon);
    }

    public void bindClick(final int position, final OnItemClickListener onItemClickListener){
        itemView.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                onItemClickListener.onItemClick(position);
            }
        });
    }

    public void bindLongClick(final int position, final OnItemLongClickListener onItemLongClickListener) {
        itemView.setOnLongClickListener(new View.OnLongClickListener() {
            @Override
            public boolean onLongClick(View view) {
                onItemLongClickListener.onItemLongClick(position);
                return true;
            }
        });
    }
}
}
like image 344
Isabelle Avatar asked Jul 28 '16 09:07

Isabelle


1 Answers

Okay so you're calling adapter.notifyDataSetChanged(), so any other notify___ method is unnecessary (data set change disables animations anyways.)

In which case the easiest (and most efficient) way of doing things would be to use the RealmResults directly rather than retrieving every element into an ArrayList which is then used the exact same way.

So it should be like this

public static RealmResults<City> getStoredCities(){
        RealmQuery<City> query = getRealmInstance().where(City.class);
        return realm.where(City.class)
                        .findAllSorted("timestamp", Sort.DESCENDING);
}

And

public static void removeCity(City city){
        final String cityName = city.getCityName();
        realm.executeTransaction(new Realm.Transaction() {
            @Override
            public void execute(Realm realm) {
                RealmResults<City> result = realm.where(City.class).equalTo("cityName", cityName).findAll();
                result.deleteAllFromRealm();
            }
        });
}

And

builder.setPositiveButton(getString(R.string.ok), new DialogInterface.OnClickListener() {
    @Override
    public void onClick(DialogInterface dialogInterface, int i) {
        RealmHelper.removeCity(getItem(position));
    }
});

And

// dependency: compile 'io.realm:android-adapters:1.3.0' 
// <-- for Realm 3.x+, use 2.0.0
// for Realm 5.x+, use 3.0.0
public class CityListAdapter extends RealmRecyclerViewAdapter<City, CityListViewHolder> { 

    OnItemClickListener onItemClickListener;
    OnItemLongClickListener onItemLongClickListener;

    public CityListAdapter(@NonNull Context context, 
                           @Nullable OrderedRealmCollection<City> data, 
                           OnItemClickListener onItemClickListener, 
                           OnItemLongClickListener onItemLongClickListener) {
        super(context, data, true);
        this.onItemClickListener = onItemClickListener;
        this.onItemLongClickListener = onItemLongClickListener;
    }


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

    @Override
    public void onBindViewHolder(CityListViewholder holder, int position) {
        holder.cityName.setText(getItem(position).getCityName());
        holder.bindClick(position, onItemClickListener);
        holder.bindLongClick(position, onItemLongClickListener);
    }

    public static class CityListViewholder extends RecyclerView.ViewHolder {
        TextView cityName;
        ImageView cityIcon;

        public CityListViewholder(View itemView, 
                                  Context context) {
            super(itemView);
            cityName = (TextView)itemView.findViewById(R.id.city_name);
            cityIcon = (ImageView)itemView.findViewById(R.id.city_icon);
        }

        public void bindClick(final int position, final OnItemClickListener onItemClickListener){
            itemView.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View view) {
                    onItemClickListener.onItemClick(position);
                }
            });
        }

        public void bindLongClick(final int position, final OnItemLongClickListener onItemLongClickListener) {
            itemView.setOnLongClickListener(new View.OnLongClickListener() {
                @Override
                public boolean onLongClick(View view) {
                    onItemLongClickListener.onItemLongClick(position);
                    return true;
                }
            });
        }
    }
}

Using RealmRecyclerViewAdapter will call notifyDataSetChanged() whenever your results change.

like image 114
EpicPandaForce Avatar answered Sep 23 '22 08:09

EpicPandaForce