1

I am creating a notes app with java, i added room database to my app and when user saves a notes it adds it to database but doesn't shows in recyclerView immediately, when i reloads the app then it shows up, how and where should i insert notifyiteminserted so that recyclerView changes immediately

I have tried onResume Method but that results in app crash. This is my MainActivity.


import android.annotation.SuppressLint;
import android.content.Intent;
import android.os.Bundle;
import android.widget.Button;
import android.widget.Toast;

import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.widget.Toolbar;
import androidx.lifecycle.Observer;
import androidx.lifecycle.ViewModelProvider;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import androidx.recyclerview.widget.StaggeredGridLayoutManager;

import java.util.ArrayList;
import java.util.List;


public class MainActivity extends AppCompatActivity {


    static ArrayList<Notes> arrNotes;
    @SuppressLint("StaticFieldLeak")
    RecyclerViewAdapter adapter;
    RecyclerView.LayoutManager layoutManager;
    notesModelView modelView;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        DatabaseHelper dbHelper = DatabaseHelper.getDatabase(this);

        arrNotes = (ArrayList<Notes>) dbHelper.notesDao().getAllNotes();


        RecyclerView recyclerView = findViewById(R.id.recycler_view);
        adapter = new RecyclerViewAdapter(this, arrNotes);
        recyclerView.setAdapter(adapter);

        //setting up recycler view
        layoutManager = new StaggeredGridLayoutManager(2, LinearLayoutManager.VERTICAL);
        recyclerView.setLayoutManager(layoutManager);

        //Setting custom Toolbar
        Toolbar toolbar1 = findViewById(R.id.toolbar);
        setSupportActionBar(toolbar1);

        //Moving from MainActivity to add_Notes Activity
        Button button = findViewById(R.id.floatingActionButton);
        button.setOnClickListener(view -> {
            Intent intent = new Intent(MainActivity.this, addActivity.class);
            startActivity(intent);

        });

    }
}


This is my add_Activity.

package com.example.keepnotes;

import android.os.Bundle;
import android.widget.Button;
import android.widget.EditText;
import android.widget.Toast;

import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.widget.Toolbar;

import java.util.Objects;

public class addActivity extends AppCompatActivity {


    MainActivity mainActivity;


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

        Toolbar toolbar_add = findViewById(R.id.toolbar_add_activity);
        setSupportActionBar(toolbar_add);
        Objects.requireNonNull(getSupportActionBar()).setDisplayHomeAsUpEnabled(true);
        toolbar_add.setNavigationIcon(R.drawable.back_button);
        toolbar_add.setNavigationOnClickListener(view -> onBackPressed());

        EditText titleText = findViewById(R.id.add_activity_title);
        EditText bodyText = findViewById(R.id.add_activity_text);
        Button saveBtn = findViewById(R.id.button);
        DatabaseHelper database = DatabaseHelper.getDatabase(this);


        saveBtn.setOnClickListener(view -> {
            String titleBody = titleText.getText().toString();
            String textBody = bodyText.getText().toString();

            if (titleBody.equals("") && textBody.equals("")) {
                Toast.makeText(addActivity.this, "Fields can't be empty",
                        Toast.LENGTH_LONG).show();
            } else {
                database.notesDao().addNotes(new Notes(titleBody, textBody));
                finish();
            }
        });
    }
}

How can i notify adapter the changes on each item add in database.

Here is my MainActivity after update to liveData.


import android.annotation.SuppressLint;
import android.content.Intent;
import android.os.Bundle;
import android.widget.Button;
import android.widget.Toast;

import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.widget.Toolbar;
import androidx.lifecycle.Observer;
import androidx.lifecycle.ViewModelProvider;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import androidx.recyclerview.widget.StaggeredGridLayoutManager;

import java.util.ArrayList;
import java.util.List;


public class MainActivity extends AppCompatActivity {


    static ArrayList<Notes> arrNotes;
    @SuppressLint("StaticFieldLeak")
    RecyclerViewAdapter adapter;
    RecyclerView.LayoutManager layoutManager;
    notesModelView modelView;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        DatabaseHelper dbHelper = DatabaseHelper.getDatabase(this);

        modelView = new ViewModelProvider(this).get(notesModelView.class);
        modelView.getAllNotes().observe(this, new Observer<List<Notes>>() {
            @Override
            public void onChanged(List<Notes> notes) {
                arrNotes = (ArrayList<Notes>) notes;
            }
        });

        
        arrNotes = (ArrayList<Notes>) dbHelper.notesDao().getAllNotes();


        RecyclerView recyclerView = findViewById(R.id.recycler_view);
        adapter = new RecyclerViewAdapter(this, arrNotes);
        recyclerView.setAdapter(adapter);

        //setting up recycler view
        layoutManager = new StaggeredGridLayoutManager(2, LinearLayoutManager.VERTICAL);
        recyclerView.setLayoutManager(layoutManager);

        //Setting custom Toolbar
        Toolbar toolbar1 = findViewById(R.id.toolbar);
        setSupportActionBar(toolbar1);

        //Moving from MainActivity to add_Notes Activity
        Button button = findViewById(R.id.floatingActionButton);
        button.setOnClickListener(view -> {
            Intent intent = new Intent(MainActivity.this, addActivity.class);
            startActivity(intent);

        });

    }
}

this is Dao.


import androidx.lifecycle.LiveData;
import androidx.room.Dao;
import androidx.room.Delete;
import androidx.room.Insert;
import androidx.room.Query;
import androidx.room.Update;

import java.util.List;

@Dao
public interface NotesDao {

    @Query("SELECT * FROM notesTable")
    List<Notes> getAllNotes();

    @Query("SELECT * FROM notesTable")
    LiveData<List<Notes>> findAllNotes();

    @Insert
    void addNotes(Notes note);

    @Update
    void updateNotes(Notes note);

    @Delete
    void deleteNotes(Notes note);



}

And here is my ViewModel

package com.example.keepnotes;

import android.app.Application;

import androidx.annotation.NonNull;
import androidx.lifecycle.AndroidViewModel;
import androidx.lifecycle.LiveData;

import java.util.List;

public class notesModelView extends AndroidViewModel {

    DatabaseHelper databaseHelper;

    public notesModelView(@NonNull Application application) {
        super(application);
        databaseHelper = DatabaseHelper.getDatabase(application.getApplicationContext());
    }

    public LiveData<List<Notes>> getAllNotes() {
        return databaseHelper.notesDao().findAllNotes();
    }

}

Here is my RecyclerView adapter

package com.example.keepnotes;

import android.annotation.SuppressLint;
import android.app.AlertDialog;
import android.content.Context;
import android.content.DialogInterface;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;

import androidx.annotation.NonNull;
import androidx.cardview.widget.CardView;
import androidx.recyclerview.widget.RecyclerView;

import java.util.ArrayList;
import java.util.List;

public class RecyclerViewAdapter extends RecyclerView.Adapter<RecyclerViewAdapter.ViewHolder> {

    Context context;
    ArrayList<Notes> arrNotes;
    DatabaseHelper databaseHelper;

    RecyclerViewAdapter(Context context, ArrayList<Notes> arrNotes, DatabaseHelper databaseHelper) {
        this.context = context;
        this.arrNotes = arrNotes;
        this.databaseHelper = databaseHelper;
    }

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

    @Override
    public void onBindViewHolder(@NonNull ViewHolder holder, @SuppressLint("RecyclerView") int position) {
        holder.title.setText(arrNotes.get(position).title);
        holder.body.setText(arrNotes.get(position).text);
        holder.index.setText(String.valueOf(position + 1));

        holder.llView.setOnLongClickListener(new View.OnLongClickListener() {
            @Override
            public boolean onLongClick(View view) {

                AlertDialog.Builder alert = new AlertDialog.Builder(context)
                        .setTitle("Delete view")
                        .setMessage("Are you sure to delete")
                        .setIcon(R.drawable.ic_baseline_delete_24)
                        .setPositiveButton("yes", new DialogInterface.OnClickListener() {
                            @Override
                            public void onClick(DialogInterface dialogInterface, int i) {
                                
                                databaseHelper.notesDao().deleteNotes(new Notes(arrNotes.get(position).id,arrNotes.get(position).title,arrNotes.get(position).text));
                                notifyItemRemoved(position);
                                notifyItemRangeChanged(position, arrNotes.size());
                            }
                        })
                        .setNegativeButton("No", new DialogInterface.OnClickListener() {
                            @Override
                            public void onClick(DialogInterface dialogInterface, int i) {

                            }
                        });
                alert.show();


                return true;
            }
        });

    }

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

    public class ViewHolder extends RecyclerView.ViewHolder {

        TextView title, body, index;
        CardView llView;

        public ViewHolder(View itemView) {
            super(itemView);
            title = itemView.findViewById(R.id.text_title_view);
            body = itemView.findViewById(R.id.text_text_view);
            index = itemView.findViewById(R.id.index);
            llView = itemView.findViewById(R.id.card_View);
            databaseHelper = DatabaseHelper.getDatabase(context);
        }
    }
}


It deletes the selected notes but also crashes immediately after confirming delete.

and it throws following error

    java.lang.IndexOutOfBoundsException: Inconsistency detected. Invalid view holder adapter positionViewHolder{535e3b3 position=5 id=-1, oldPos=4, pLpos:4 scrap [attachedScrap] tmpDetached not recyclable(1) no parent} androidx.recyclerview.widget.RecyclerView{11f4816 VFED..... ......ID 31,171-689,1048 #7f090165 app:id/recycler_view}, adapter:com.example.keepnotes.RecyclerViewAdapter@fd652a0, layout:androidx.recyclerview.widget.StaggeredGridLayoutManager@32e1e59, context:com.example.keepnotes.MainActivity@286bccd

  • It would be better this way: 1) Always add to/remove from Database, not recyclerview. 2) Fragment/Activity always listens to Room database changes & updates RecyclerView when there is change. 2a) To listen to changes, you need your Room DAO interface method to return some Observable/Flowable/LiveData/Flow type – Jemshit Aug 11 '22 at 10:05
  • i have used liveData but can't figure out what to write in observe method after getting data – ShahZaman Rai Aug 11 '22 at 10:08
  • https://stackoverflow.com/questions/31367599/how-to-update-recyclerview-adapter-data – Vivek Gupta Aug 11 '22 at 10:13

2 Answers2

0

Remove this two line

notifyItemRemoved(position);
notifyItemRangeChanged(position, arrNotes.size());

OLD ANSWER

First, if you use liveData you don't need to call the method

arrNotes = (ArrayList<Notes>) dbHelper.notesDao().getAllNotes();

just keep a reference to the adapter instance and whenever there is a change from liveData call the notifyDataSetChanged method.

adapter = new RecyclerViewAdapter(this,new List<Notes>());
RecyclerView recyclerView = findViewById(R.id.recycler_view);
recyclerView.setAdapter(adapter);
modelView.getAllNotes().observe(this, new Observer<List<Notes>>() {
        @Override
        public void onChanged(List<Notes> notes) {
            arrNotes.clear();
            arrNotes.addAll(notes);
            adapter.notifyDataSetChanged()
        }
    });
Graziano Rizzi
  • 160
  • 1
  • 1
  • 10
  • and what if we want to delete – ShahZaman Rai Aug 11 '22 at 12:00
  • simply do a DELETE query on the db, the livedata will return an empty array and then all elements from the recycleview will be deleted – Graziano Rizzi Aug 11 '22 at 12:05
  • databaseHelper.notesDao().deleteNotes(new Notes(arrNotes.get(position).id,arrNotes.get(position).title,arrNotes.get(position).text)); – ShahZaman Rai Aug 11 '22 at 12:11
  • i used this to delete single item, it worked but app crashes after it with a exception – ShahZaman Rai Aug 11 '22 at 12:11
  • show me the code and the exception pls. – Graziano Rizzi Aug 11 '22 at 12:18
  • however, to better manage the notes on the database you should enter a self-generated primary key and use it as the identifier of the single note so as not to have to rebuild the entire object to be able to delete a single note. add somethings like: @PrimaryKey(autoGenerate = true) int noteId; to your entity. And you can use something like @Query("DELETE FROM notesTable WHERE noteId = :noteId") void deleteNote(int noteId) – Graziano Rizzi Aug 11 '22 at 12:25
  • question Updated – ShahZaman Rai Aug 11 '22 at 12:43
  • answer updated remove this 2 line notifyItemRemoved(position); notifyItemRangeChanged(position, arrNotes.size()); – Graziano Rizzi Aug 11 '22 at 12:56
  • same error and app crashed java.lang.IndexOutOfBoundsException: Inconsistency detected. Invalid view holder adapter positionViewHolder{b6cb7f position=2 id=-1, oldPos=1, pLpos:1 scrap [attachedScrap] tmpDetached no parent} androidx.recyclerview.widget.RecyclerView{374614a VFED..... ......I. 31,171-689,814 #7f090165 app:id/recycler_view}, adapter:com.example.keepnotes.RecyclerViewAdapter@59f420d, layout:androidx.recyclerview.widget.StaggeredGridLayoutManager@dfd45c2, context:com.example.keepnotes.MainActivity@f092891 – ShahZaman Rai Aug 12 '22 at 04:49
  • Can you please tell me what's wrong with delete statement – ShahZaman Rai Aug 13 '22 at 13:15
  • @ShahZamanRai the error is strange because it should happen if you modify the contents of the list passed to the adapter at the same time by two different threads. Seeing only this piece of code I can't find the cause. You could try modifying the code to reorder it and see if you can turn it around. My advice is to avoid in any way using the database or databaseHelper from elements of ui (activity / fragment / view / adapter etc ...) and to move all calls to the DB inside the viewmodel. – Graziano Rizzi Aug 16 '22 at 07:16
  • Also I advise you to avoid writing the code that launches the dialog inside the adapter, it is better to pass a callback to the adapter to invoke and insert the code to create the dialog when you instate the adapter in the activity, it doesn't make much difference programmatically itself but allows you to reuse the same adapter on other occasions without rewriting the code. – Graziano Rizzi Aug 16 '22 at 07:17
  • The other thing I recommend, even if not for this project since it would force you to rewrite everything, is to switch to Kotlin and the coroutine which would make it much easier for you to manage the tasks to be performed on the different threads. Remove all references to the Helper database from the various elements of ui and then if the error is not resolved we try to review the code again. – Graziano Rizzi Aug 16 '22 at 07:17
-1

observe a LiveData object as it is not a LifecycleOwner

Use observeForever() on the LiveData, manually unregistering via removeObserver() when appropriate (onDestroy() of the service, if not sooner).

Bear in mind that standard service limitations apply here (e.g., services run for ~1 minute on Android 8.0+ unless they are foreground services), so it may be that you need to consider other approaches anyway.

Also you can read more in original post in here -> Link

Milad Targholi
  • 102
  • 1
  • 8