0

Can't seem to figure this out, I have gone through all the code without any success. When I add a new bowler to an empty list it appears correctly, when I add a second or more bowlers to the list, the name of the bowler shows up as the name of the first bowler added. I have included several screenshots below to show what I mean.

enter image description here enter image description here enter image description here enter image description here

Obviously the order of these images goes left to right. I have include my BowlerActivity to this post as well. This is where I am creating and updating new and existing bowlers.

private BowlerAdapter mAdapter;
    private List<Bowler> bowlersList = new ArrayList<>();
    private CoordinatorLayout coordinatorLayout;
    private RecyclerView recyclerView;
    private TextView noBowlersView;

    private DatabaseHelper db;

    private TextView leagueId;
    private String savedLeagueId;


    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_bowler);
        Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
        setSupportActionBar(toolbar);
        getSupportActionBar().setDisplayHomeAsUpEnabled(true);
        getSupportActionBar().setDisplayShowHomeEnabled(true);

        toolbar.setNavigationOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                startActivity(new Intent(getApplicationContext(),MainActivity.class));
                finish();
            }
        });

        savedLeagueId = String.valueOf(getIntent().getIntExtra("leagueId",2));
        leagueId = (TextView) findViewById(R.id.tvLeagueId);

        coordinatorLayout = findViewById(R.id.coordinator_layout);
        recyclerView = findViewById(R.id.recycler_view);
        noBowlersView = findViewById(R.id.empty_bowlers_view);

        db = new DatabaseHelper(this);

        bowlersList.addAll(db.getAllBowlers(savedLeagueId));

        FloatingActionButton fab = (FloatingActionButton) findViewById(R.id.add_bowler_fab);
        fab.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                showBowlerDialog(false, null, -1);
            }
        });

        mAdapter = new BowlerAdapter(this, bowlersList);
        RecyclerView.LayoutManager mLayoutManager = new LinearLayoutManager(getApplicationContext());
        recyclerView.setLayoutManager(mLayoutManager);
        recyclerView.setItemAnimator(new DefaultItemAnimator());
        recyclerView.addItemDecoration(new MyDividerItemDecoration(this, LinearLayoutManager.VERTICAL, 16));
        recyclerView.setAdapter(mAdapter);

        toggleEmptyBowlers();

        //On Long Click On The RecyclerView Item An Alert Dialog Is Opened With The Option To Choose Edit/Delete
        recyclerView.addOnItemTouchListener(new RecyclerTouchListener(this,
                recyclerView, new RecyclerTouchListener.ClickListener() {
            @Override
            public void onClick(View view, final int position) {

                String seriesLeagueId = bowlersList.get(position).getLeagueId();
                int seriesBowlerId = bowlersList.get(position).getId();
                Intent myIntent = new Intent(BowlerActivity.this, SeriesActivity.class);
                myIntent.putExtra("seriesLeagueId", seriesLeagueId);
                myIntent.putExtra("seriesBowlerId", seriesBowlerId);
                startActivity(myIntent);

            }

            @Override
            public void onLongClick(View view, int position) {
                showActionsDialog(position);
            }
        }));
    }

    //Inserting New Bowler In The Database And Refreshing The List
    private void createBowler(String leagueId,  String bowlerName) {
        //Inserting Bowler In The Database And Getting Newly Inserted Bowler Id
        long id = db.insertBowler(leagueId, bowlerName);

        //Get The Newly Inserted Bowler From The Database
        Bowler n = db.getBowler(leagueId);

        if (n != null) {
            //Adding New Bowler To The Array List At Position 0
            bowlersList.add(0, n);

            //Refreshing The List
            mAdapter.notifyDataSetChanged();

            toggleEmptyBowlers();
        }
    }

    //Updating Bowler In The Database And Updating The Item In The List By Its Position
    private void updateBowler(String bowlerName, int position) {
        Bowler n = bowlersList.get(position);

        //Updating Bowler Text
        n.setLeagueId(savedLeagueId);
        n.setName(bowlerName);

        //Updating The Bowler In The Database
        db.updateBowler(n);

        //Refreshing The List
        bowlersList.set(position, n);
        mAdapter.notifyItemChanged(position);

        toggleEmptyBowlers();
    }

    //Deleting Bowler From SQLite Database And Removing The Bowler Item From The List By Its Position
    private void deleteBowler(int position) {
        //Deleting The Bowler From The Database
        db.deleteBowler(bowlersList.get(position));

        //Removing The Bowler From The List
        bowlersList.remove(position);
        mAdapter.notifyItemRemoved(position);

        toggleEmptyBowlers();
    }

    //Opens Dialog With Edit/Delete Options
    //Edit - 0
    //Delete - 0
    private void showActionsDialog(final int position) {
        CharSequence colors[] = new CharSequence[]{"Edit", "Delete"};

        AlertDialog.Builder builder = new AlertDialog.Builder(this);
        builder.setTitle("Choose option");
        builder.setItems(colors, new DialogInterface.OnClickListener() {
            @Override
            public void onClick(DialogInterface dialog, int which) {
                if (which == 0) {
                    showBowlerDialog(true, bowlersList.get(position), position);
                } else {
                    deleteBowler(position);
                }
            }
        });
        builder.show();
    }

    //Show Alert Dialog With EditText Options to Enter/Edit A League
    //When shouldUpdate = true, It Will Automatically Display Old Bowler Name And Change The Button Text To UPDATE
    private void showBowlerDialog(final boolean shouldUpdate, final Bowler bowler, final int position) {
        LayoutInflater layoutInflaterAndroid = LayoutInflater.from(getApplicationContext());
        View view = layoutInflaterAndroid.inflate(R.layout.dialog_bowler, null);

        AlertDialog.Builder alertDialogBuilderUserInput = new AlertDialog.Builder(BowlerActivity.this);
        alertDialogBuilderUserInput.setView(view);

        leagueId.setText(savedLeagueId);
        final EditText inputBowlerName = view.findViewById(R.id.etBowlerNameInput);
        TextView dialogTitle = view.findViewById(R.id.dialog_title);
        dialogTitle.setText(!shouldUpdate ? getString(R.string.lbl_new_bowler_title) : getString(R.string.lbl_edit_bowler_title));

        if (shouldUpdate && bowler != null) {
            leagueId.setText(bowler.getLeagueId());
            inputBowlerName.setText(bowler.getName());

        }
        alertDialogBuilderUserInput
                .setCancelable(false)
                .setPositiveButton(shouldUpdate ? "update" : "save", new DialogInterface.OnClickListener() {
                    public void onClick(DialogInterface dialogBox, int id) {

                    }
                })
                .setNegativeButton("cancel",
                        new DialogInterface.OnClickListener() {
                            public void onClick(DialogInterface dialogBox, int id) {
                                dialogBox.cancel();
                            }
                        });

        final AlertDialog alertDialog = alertDialogBuilderUserInput.create();
        alertDialog.show();

        alertDialog.getButton(AlertDialog.BUTTON_POSITIVE).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {

                //Show Toast Message When No Text Is Entered
                if (TextUtils.isEmpty(inputBowlerName.getText().toString())) {
                    Toast.makeText(BowlerActivity.this, "Enter Bowler!", Toast.LENGTH_SHORT).show();
                    return;
                } else {
                    alertDialog.dismiss();
                }

                //Check If User Is Updating Bowler
                if (shouldUpdate && bowler != null) {

                    //Updating Bowler By Its Id
                    updateBowler(inputBowlerName.getText().toString(), position);

                } else {
                    //Creating New Bowler
                    createBowler(leagueId.getText().toString(), inputBowlerName.getText().toString());

                }
            }
        });
    }

    //Toggling List And Empty Bowler View
    private void toggleEmptyBowlers() {
        //You Can Check bowlerList.size() > 0

        if (db.getBowlersCount() > 0) {
            noBowlersView.setVisibility( View.GONE);
        } else {
            noBowlersView.setVisibility( View.VISIBLE);
        }
    }
    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        // Inflate the menu; this adds items to the action bar if it is present.
        getMenuInflater().inflate( R.menu.menu_main, menu );
        return true;
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        // Handle action bar item clicks here. The action bar will
        // automatically handle clicks on the Home/Up button, so long
        // as you specify a parent activity in AndroidManifest.xml.
        int id = item.getItemId();

        //noinspection SimplifiableIfStatement
        if (id == R.id.action_settings) {
            return true;
        }

        return super.onOptionsItemSelected( item );
    }
}

I have also include the DatabaseHelper portion that contains the Bowler methods.

public long insertBowler(String leagueId, String bowlerName) {
        //Get Writable Database That We Want To Write Data Too
        SQLiteDatabase db = this.getWritableDatabase();

        ContentValues values = new ContentValues();
        //`id` and `timestamp` Will Be Inserted Automatically
        values.put(Bowler.COLUMN_LEAGUE_ID, leagueId);
        values.put(Bowler.COLUMN_NAME, bowlerName);

         //Insert Row
        long id = db.insert( Bowler.TABLE_NAME, null, values );

        //Close Database Connection
        db.close();

        //Return Newly Inserted Row Id
        return id;
    }

    public Bowler getBowler(String leagueId) {
        //Get Readable Database If We Are Not Inserting Anything
        SQLiteDatabase db = this.getReadableDatabase();

        Cursor cursor = db.query(Bowler.TABLE_NAME,
                new String[]{Bowler.COLUMN_ID, Bowler.COLUMN_LEAGUE_ID, Bowler.COLUMN_NAME, Bowler.COLUMN_TIMESTAMP},
                Bowler.COLUMN_LEAGUE_ID + "=?",
                new String[]{String.valueOf(leagueId)}, null, null, null, null);

        if (cursor.moveToFirst()) {

            //Prepare Bowler Object
        Bowler bowler = new Bowler(
                cursor.getInt(cursor.getColumnIndex(Bowler.COLUMN_ID)),
                cursor.getString(cursor.getColumnIndex(Bowler.COLUMN_LEAGUE_ID)),
                cursor.getString(cursor.getColumnIndex(Bowler.COLUMN_NAME)),
                cursor.getString(cursor.getColumnIndex(Bowler.COLUMN_TIMESTAMP)));

        //Close Database Connection
        cursor.close();
        return bowler;
    } else {return null;}
}
    public List<Bowler> getAllBowlers(String leagueId) {
        List<Bowler> bowlers = new ArrayList<>();

        //Select All Query
        String selectQuery = "SELECT  * FROM " + Bowler.TABLE_NAME + " WHERE " + Bowler.COLUMN_LEAGUE_ID + " = '" + leagueId + "'" + " ORDER BY " +
                Bowler.COLUMN_TIMESTAMP + " DESC";

        SQLiteDatabase db = this.getWritableDatabase();
        Cursor cursor = db.rawQuery(selectQuery, null);

        //Looping Through All Rows And Adding To The List
        if (cursor.moveToFirst()) {
            do {
                Bowler bowler = new Bowler();
                bowler.setId(cursor.getInt(cursor.getColumnIndex(Bowler.COLUMN_ID)));
                bowler.setLeagueId(cursor.getString(cursor.getColumnIndex(Bowler.COLUMN_LEAGUE_ID)));
                bowler.setName(cursor.getString(cursor.getColumnIndex(Bowler.COLUMN_NAME)));
                bowler.setTimestamp(cursor.getString(cursor.getColumnIndex(Bowler.COLUMN_TIMESTAMP)));
                bowlers.add(bowler);
            } while (cursor.moveToNext());
        }
        cursor.close();
        //Close Database Connection
        db.close();

        //Return Bowlers List
        return bowlers;
    }

    public int getBowlersCount() {
        String countQuery = "SELECT  * FROM " + Bowler.TABLE_NAME;
        SQLiteDatabase db = this.getReadableDatabase();
        Cursor cursor = db.rawQuery(countQuery, null);

        int count = cursor.getCount();
        cursor.close();

        //Return The Count
        return count;
    }

    public int updateBowler(Bowler bowler) {
        SQLiteDatabase db = this.getWritableDatabase();

        ContentValues values = new ContentValues();
        values.put(Bowler.COLUMN_LEAGUE_ID, bowler.getLeagueId());
        values.put(Bowler.COLUMN_NAME, bowler.getName());

        //Updating Row
        return db.update(Bowler.TABLE_NAME, values, Bowler.COLUMN_ID + " = ?",
                new String[]{String.valueOf(bowler.getId())});
    }

    public void deleteBowler(Bowler bowler) {
        SQLiteDatabase db = this.getWritableDatabase();
        db.delete( Bowler.TABLE_NAME, Bowler.COLUMN_ID + " = ?",
                new String[]{String.valueOf( bowler.getId())});
        db.close();
    }

And just in case its required I have posted the BowlerAdapter code as well.

public class BowlerAdapter extends RecyclerView.Adapter<BowlerAdapter.MyViewHolder> {

    private Context context;
    private List<Bowler> bowlersList;

    public class MyViewHolder extends RecyclerView.ViewHolder {
        public TextView bowlerLeagueId;
        public TextView name;
        public TextView timestamp;

        public MyViewHolder(View view) {
            super(view);
            bowlerLeagueId = view.findViewById( R.id.tvLeagueId);
            name = view.findViewById(R.id.tvBowlerName );
            timestamp = view.findViewById(R.id.timestamp);
        }
    }


    public BowlerAdapter(Context context, List<Bowler> bowlersList) {
        this.context = context;
        this.bowlersList = bowlersList;
    }

    @Override
    public MyViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        View itemView = LayoutInflater.from(parent.getContext())
                .inflate(R.layout.listview_bowler, parent, false);

        return new MyViewHolder(itemView);
    }

    @Override
    public void onBindViewHolder(MyViewHolder holder, int position) {
        Bowler bowler = bowlersList.get(position);

        holder.bowlerLeagueId.setText(bowler.getLeagueId());
        holder.name.setText(bowler.getName());

        //Formatting And Displaying Timestamp
        holder.timestamp.setText(formatDate(bowler.getTimestamp()));
    }

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

    //Formatting TimeStamp to 'EEE MMM dd yyyy (HH:mm:ss)'
    //Input  : 2018-05-23 9:59:01
    //Output : Wed May 23 2018 (9:59:01)
    private String formatDate(String dateStr) {
        try {
            SimpleDateFormat fmt = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
            Date date = fmt.parse(dateStr);
            SimpleDateFormat fmtOut = new SimpleDateFormat("EEE MMM dd yyyy (HH:mm:ss)");
            return fmtOut.format(date);
        } catch (ParseException e) { }

        return "";
    }

I am sure its is something very obvious, but I have been looking at the code for a few hours and I just don't see the issue.

Any assistance would be appreciated.

I have amended my code as you indicated, but when I add a new bowler now the screen comes back blank until I leave the BowlerActivity and come back into it, at this point the list has been updated.

//Inserting New Bowler In The Database And Refreshing The List
    private void createBowler(String leagueId,  String bowlerName) {
        //Inserting Bowler In The Database And Getting Newly Inserted Bowler Id
        long id = db.insertBowler(leagueId, bowlerName);

        //Get The Newly Inserted Bowler From The Database
        Bowler n = db.getBowler(leagueId);

        if (n != null) {
            //Adding New Bowler To The Array List At Position 0
            bowlersList.add(0, n);

            //Refreshing The List
            mAdapter.notifyDataSetChanged(bowlersList);

            toggleEmptyBowlers();
        }
    }

BowlerAdapter notifyDataSetChanged method

private List<Bowler> bowlersList;

    public void notifyDataSetChanged(List<Bowler> newbowlersList) {
        bowlersList.clear();
        bowlersList.addAll( newbowlersList );
        super.notifyDataSetChanged();
    }

enter image description here enter image description here enter image description here

Debugging Logs

this = {BowlerActivity@5027} 
leagueId = "1"
bowlerName = "Robert"
id = 6
n = {Bowler@5032} 
 id = 1
 league_id = "1"
 name = "b1"
 timestamp = "2018-06-07 20:03:19"
 shadow$_klass_ = {Class@4839} "class ca.rvogl.tpbcui.database.models.Bowler"
 shadow$_monitor_ = -2106571381
mAdapter = {BowlerAdapter@5033} 
 bowlersList = {ArrayList@5037}  size = 0
 mHasStableIds = false
 mObservable = {RecyclerView$AdapterDataObservable@5088} 
 shadow$_klass_ = {Class@4930} "class ca.rvogl.tpbcui.views.BowlerAdapter"
 shadow$_monitor_ = -2088753560

log.d log file snippet

06-08 13:00:53.807 29879-29879/ca.rvogl.tpbcui D/INSERTBOWLER: Number of bowlers in db = 4
06-08 13:00:53.814 29879-29884/ca.rvogl.tpbcui I/art: Do partial code cache collection, code=30KB, data=29KB
06-08 13:00:53.815 29879-29884/ca.rvogl.tpbcui I/art: After code cache collection, code=30KB, data=29KB
    Increasing code cache capacity to 128KB
06-08 13:00:53.815 29879-29895/ca.rvogl.tpbcui D/EGL_emulation: eglMakeCurrent: 0x9e6fa300: ver 3 0 (tinfo 0xa068a610)
06-08 13:00:53.831 29879-29879/ca.rvogl.tpbcui W/IInputConnectionWrapper: finishComposingText on inactive InputConnection
06-08 13:01:00.552 29879-29895/ca.rvogl.tpbcui D/EGL_emulation: eglMakeCurrent: 0x9e6fa300: ver 3 0 (tinfo 0xa068a610)
06-08 13:01:02.336 29879-29895/ca.rvogl.tpbcui D/EGL_emulation: eglMakeCurrent: 0x9e6fa300: ver 3 0 (tinfo 0xa068a610)
06-08 13:01:04.023 29879-29895/ca.rvogl.tpbcui D/EGL_emulation: eglMakeCurrent: 0x9e6fa300: ver 3 0 (tinfo 0xa068a610)
06-08 13:01:04.040 29879-29895/ca.rvogl.tpbcui D/EGL_emulation: eglMakeCurrent: 0x9e6fa300: ver 3 0 (tinfo 0xa068a610)
06-08 13:01:04.050 29879-29895/ca.rvogl.tpbcui D/EGL_emulation: eglMakeCurrent: 0x9e6fa300: ver 3 0 (tinfo 0xa068a610)
06-08 13:01:04.059 29879-29895/ca.rvogl.tpbcui D/EGL_emulation: eglMakeCurrent: 0x9e6fa300: ver 3 0 (tinfo 0xa068a610)
06-08 13:01:04.534 29879-29895/ca.rvogl.tpbcui D/EGL_emulation: eglMakeCurrent: 0x9e6fa300: ver 3 0 (tinfo 0xa068a610)
06-08 13:01:04.542 29879-29895/ca.rvogl.tpbcui D/EGL_emulation: eglMakeCurrent: 0x9e6fa300: ver 3 0 (tinfo 0xa068a610)
06-08 13:01:05.034 29879-29895/ca.rvogl.tpbcui D/EGL_emulation: eglMakeCurrent: 0x9e6fa300: ver 3 0 (tinfo 0xa068a610)
06-08 13:01:11.363 29879-29895/ca.rvogl.tpbcui D/EGL_emulation: eglMakeCurrent: 0x9e6fa300: ver 3 0 (tinfo 0xa068a610)
06-08 13:01:11.373 29879-29879/ca.rvogl.tpbcui D/INSERTBOWLER: Number of bowlers in db = 5
06-08 13:01:11.380 29879-29895/ca.rvogl.tpbcui D/EGL_emulation: eglMakeCurrent: 0x9e6fa300: ver 3 0 (tinfo 0xa068a610)
06-08 13:01:11.393 29879-29879/ca.rvogl.tpbcui W/IInputConnectionWrapper: finishComposingText on inactive InputConnection
Robert Vogl
  • 250
  • 5
  • 19
  • use adapter.notifyDataSetChanged(), Also take a look a this: https://stackoverflow.com/questions/14503006/android-listview-not-refreshing-after-notifydatasetchanged – Zombie Jun 06 '18 at 15:37
  • I already have a mAdapter.notifyDataSetChange() in my code. It doesn't seem to affect it. – Robert Vogl Jun 06 '18 at 17:07

1 Answers1

1

I believe that your issue is that you are not updating the actual source List within the adapter.

A resolution could be to be add a method to the BowlerAdapter e.g :-

public void notifyDatasetChanged(List<Bowler> newbowlerlist) {
    bowlersList.clear();
    bowlersList.addAll(newbowlerlist);
    super.notifyDataSetChanged();
}

And to then use this new method, passing the amended bowlerlist, rather than the stock notifyDatasetChanged method.

e.g. instead of :-

    if (n != null) {
        //Adding New Bowler To The Array List At Position 0
        bowlersList.add(0, n);

        //Refreshing The List
        mAdapter.notifyDataSetChanged();

        toggleEmptyBowlers();
    }

use :-

    if (n != null) {
        //Adding New Bowler To The Array List At Position 0
        bowlersList.add(0, n);

        //Refreshing The List
        mAdapter.notifyDataSetChanged(bowlerList);

        toggleEmptyBowlers();
    }

Based upon you code using the above, the following results are obtained:-

Initial Display

enter image description here

  • 2 Bowlers initially added when App starts.
  • Added button rather than code all the dialog handling (so clicking the button will add a new bowler, if the EditText isn't blank).

After Adding new Bowler

A new bowler was added by :-

  1. Entering Howard in the EditText.
  2. Clicking the Add Bowler button.
  3. the List is refreshed displaying the added Bowler.

:-

enter image description here

Addition re comment :-

OK, insert is happening OK (you can remove the changes). Now make the following changes to the getAllBowlers method (1 write the selectQuery sql to the log, 2 write the number of rows retrieved to the log and 3 write the size of the bowlers ArrayList to the log). Run and report or fix.

public List<Bowler> getAllBowlers(String leagueId) {
    List<Bowler> bowlers = new ArrayList<>();

    //Select All Query
    String selectQuery = "SELECT  * FROM " + Bowler.TABLE_NAME + " WHERE " + Bowler.COLUMN_LEAGUE_ID + " = '" + leagueId + "'" + " ORDER BY " +
            Bowler.COLUMN_TIMESTAMP + " DESC";

    Log.d("GETALLBOWLERS-SQL","SQL used = >>>>" +selectQuery + "<<<<");

    SQLiteDatabase db = this.getWritableDatabase();
    Cursor cursor = db.rawQuery(selectQuery, null);

    Log.d("GETALLBOWLERS-CNT","Number of rows retrieved = " + String.valueOf(cursor.getCount()));


    //Looping Through All Rows And Adding To The List
    if (cursor.moveToFirst()) {
        do {
            Bowler bowler = new Bowler();
            bowler.setId(cursor.getInt(cursor.getColumnIndex(Bowler.COLUMN_ID)));
            bowler.setLeagueId(cursor.getString(cursor.getColumnIndex(Bowler.COLUMN_LEAGUE_ID)));
            bowler.setName(cursor.getString(cursor.getColumnIndex(Bowler.COLUMN_NAME)));
            bowler.setTimestamp(cursor.getString(cursor.getColumnIndex(Bowler.COLUMN_TIMESTAMP)));
            bowlers.add(bowler);
        } while (cursor.moveToNext());
    }
    cursor.close();
    //Close Database Connection
    db.close();
    Log.d("GETALLBOWLERS-CNT","Number of elements in bowlerslist = " + String.valueOf(bowlers.size()));

    //Return Bowlers List
    return bowlers;
}

Working Example

I can't actually pinpoint your issue, although I do believe it's related to the original answer in that the Adapter works on/with a copy of of the bowler list, which is different to the bowler list passed to it. As such changing the bowlerlist in the activity does not change the bowlerlist in the adapter. As such issuing a onNotifyDatasetChanged after changing the bowlerlist is saying to the adapter that you've change the list in the adapter (i.e it's copy, which hasn't been changed).

As such the copy on the bowler list has to be changed and then the onNotifyDatasetChanged should be issued. My guess is that you didn't correctly implement the above.

As such I have virtually re-created your code and have a version that works :-

  • Instead of using an action bar a button is used to add a bowler.
  • Instead of this being invoked by anther activity and getting the LeagueID from the Intent Extra, the LeagueID is hard coded.
  • the ClickListener interface is included within the activity for my convenience.

The activity (I've called it BowlerActivity)

public class BowlerActivity extends AppCompatActivity {

    private BowlerAdapter mAdapter;
    private List<Bowler> bowlersList = new ArrayList<>();
    private RecyclerView recyclerView;
    private DatabaseHelper db;
    private Button addbowler;
    private EditText newbowler;
    private TextView leagueId, noBowlersView;
    private String savedLeagueId;
    Context mContext;


    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        mContext = this;
        addbowler = this.findViewById(R.id.addbowler); // For testing
        leagueId = this.findViewById(R.id.tvLeagueId);
        noBowlersView = this.findViewById(R.id.noBowlersView);
        recyclerView = this.findViewById(R.id.recycler_view);
        db = new DatabaseHelper(this);

        savedLeagueId = "0"; //<<<< HARD CODED rather than get from IntentExtra
        leagueId.setText(savedLeagueId);
        bowlersList = db.getAllBowlers(leagueId.getText().toString());
        RecyclerView.LayoutManager mLayoutManager = new LinearLayoutManager(this);
        mAdapter = new BowlerAdapter(this,bowlersList);
        recyclerView.setLayoutManager(mLayoutManager);
        recyclerView.setItemAnimator(new DefaultItemAnimator());
        //recyclerView.addItemDecoration(new MyDividerItemDecoration(this, LinearLayoutManager.VERTICAL, 16));
        recyclerView.setAdapter(mAdapter);

        addbowler.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                showBowlerDialog(false, null, -1);
            }
        });

        recyclerView.addOnItemTouchListener(new RecyclerTouchListener(this, recyclerView, new ClickListener() {
            @Override
            public void onClick(View view, int position) {
                Toast.makeText(mContext,"You clicked me", Toast.LENGTH_SHORT).show();
            }

            @Override
            public void onLongClick(View view, int position) {
                Toast.makeText(mContext,"You long clicked me", Toast.LENGTH_SHORT).show();
                showActionsDialog(position);

            }
        }));
    }

    //Opens Dialog With Edit/Delete Options
    //Edit - 0
    //Delete - 0
    private void showActionsDialog(final int position) {
        CharSequence colors[] = new CharSequence[]{"Edit", "Delete"};

        AlertDialog.Builder builder = new AlertDialog.Builder(this);
        builder.setTitle("Choose option");
        builder.setItems(colors, new DialogInterface.OnClickListener() {
            @Override
            public void onClick(DialogInterface dialog, int which) {
                if (which == 0) {
                    showBowlerDialog(true, bowlersList.get(position), position);
                } else {
                    deleteBowler(position);
                }
            }
        });
        builder.show();
    }

    //Toggling List And Empty Bowler View
    private void toggleEmptyBowlers() {
        //You Can Check bowlerList.size() > 0

        if (db.getBowlersCount() > 0) {
            noBowlersView.setVisibility( View.GONE);
        } else {
            noBowlersView.setVisibility( View.VISIBLE);
        }
    }

    //Inserting New Bowler In The Database And Refreshing The List
    private void createBowler(String leagueId,  String bowlerName) {
        //Inserting Bowler In The Database And Getting Newly Inserted Bowler Id
        long id = db.insertBowler(leagueId, bowlerName);

        //Get The Newly Inserted Bowler From The Database
       bowlersList = db.getAllBowlers(leagueId);
       mAdapter.notifyDatasetChanged(bowlersList);
       //mAdapter.notifyDataSetChanged();
    }

    //Updating Bowler In The Database And Updating The Item In The List By Its Position
    private void updateBowler(String bowlerName, int position) {
        Bowler n = bowlersList.get(position);

        //Updating Bowler Text
        n.setLeagueId(savedLeagueId);
        n.setName(bowlerName);

        //Updating The Bowler In The Database
        db.updateBowler(n);

        //Refreshing The List
        bowlersList.set(position, n); //<<<<< does not change the bowlerlist in the adapter
        //mAdapter.notifyItemChanged(position); // Saying that nothing has changed
        mAdapter.notifyDatasetChanged(db.getAllBowlers(savedLeagueId)); //<<<<< rebuilds adapter bowler list
        toggleEmptyBowlers();
    }

    //Deleting Bowler From SQLite Database And Removing The Bowler Item From The List By Its Position
    private void deleteBowler(int position) {
        //Deleting The Bowler From The Database
        db.deleteBowler(bowlersList.get(position));


        //Removing The Bowler From The List
        bowlersList.remove(position);
        //mAdapter.notifyItemRemoved(position); // Saying that nothing has changed
        mAdapter.notifyDatasetChanged(db.getAllBowlers(savedLeagueId));
        toggleEmptyBowlers();
    }

    public interface ClickListener{
        void onClick(View view,int position);
        void onLongClick(View view,int position);
    }

    //Show Alert Dialog With EditText Options to Enter/Edit A League
    //When shouldUpdate = true, It Will Automatically Display Old Bowler Name And Change The Button Text To UPDATE
    private void showBowlerDialog(final boolean shouldUpdate, final Bowler bowler, final int position) {
        LayoutInflater layoutInflaterAndroid = LayoutInflater.from(getApplicationContext());
        View view = layoutInflaterAndroid.inflate(R.layout.dialog_bowler, null);

        AlertDialog.Builder alertDialogBuilderUserInput = new AlertDialog.Builder(BowlerActivity.this);
        alertDialogBuilderUserInput.setView(view);

        leagueId.setText(savedLeagueId);
        final EditText inputBowlerName = view.findViewById(R.id.etBowlerNameInput);
        TextView dialogTitle = view.findViewById(R.id.dialog_title);
        dialogTitle.setText(!shouldUpdate ? getString(R.string.lbl_new_bowler_title) : getString(R.string.lbl_edit_bowler_title));

        if (shouldUpdate && bowler != null) {
            leagueId.setText(bowler.getLeagueId());
            inputBowlerName.setText(bowler.getName());

        }
        alertDialogBuilderUserInput
                .setCancelable(false)
                .setPositiveButton(shouldUpdate ? "update" : "save", new DialogInterface.OnClickListener() {
                    public void onClick(DialogInterface dialogBox, int id) {

                    }
                })
                .setNegativeButton("cancel",
                        new DialogInterface.OnClickListener() {
                            public void onClick(DialogInterface dialogBox, int id) {
                                dialogBox.cancel();
                            }
                        });

        final AlertDialog alertDialog = alertDialogBuilderUserInput.create();
        alertDialog.show();

        alertDialog.getButton(AlertDialog.BUTTON_POSITIVE).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {

                //Show Toast Message When No Text Is Entered
                if (TextUtils.isEmpty(inputBowlerName.getText().toString())) {
                    Toast.makeText(BowlerActivity.this, "Enter Bowler!", Toast.LENGTH_SHORT).show();
                    return;
                } else {
                    alertDialog.dismiss();
                }

                //Check If User Is Updating Bowler
                if (shouldUpdate && bowler != null) {

                    //Updating Bowler By Its Id
                    updateBowler(inputBowlerName.getText().toString(), position);

                } else {
                    //Creating New Bowler
                    createBowler(leagueId.getText().toString(), inputBowlerName.getText().toString());
                }
            }
        });
    }
}
  • really the only change is using the onNotifyDatasetChanged(List<Bowler>) method rather than the stock onNotifyDatabsetChanged()

BowlerAdapter

public class BowlerAdapter extends RecyclerView.Adapter<BowlerAdapter.MyViewHolder> {

    private Context context;
    private List<Bowler> bowlersList;

    public class MyViewHolder extends RecyclerView.ViewHolder {
        public TextView bowlerLeagueId;
        public TextView name;
        public TextView timestamp;


        public MyViewHolder(View view) {
            super(view);
            bowlerLeagueId = view.findViewById(R.id.tvLeagueId);
            name = view.findViewById(R.id.tvBowlerName);
            timestamp = view.findViewById(R.id.timestamp);
        }
    }


    public BowlerAdapter(Context context, List<Bowler> bowlersList) {
        this.context = context;
        this.bowlersList = bowlersList;
    }

    @Override
    public MyViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        int fakeid = android.R.layout.simple_list_item_1;
        int realid = R.layout.listview_boweler;

        View itemView = LayoutInflater.from(parent.getContext())
                .inflate(realid, parent, false);

        return new MyViewHolder(itemView);
    }

    @Override
    public void onBindViewHolder(MyViewHolder holder, int position) {
        Bowler bowler = bowlersList.get(position);

        holder.bowlerLeagueId.setText(bowler.getLeagueId());
        holder.name.setText(bowler.getName());

        //Formatting And Displaying Timestamp
        holder.timestamp.setText(formatDate(bowler.getTimestamp()));
    }

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

    //<<<<<<<<<< Added >>>>>>>>>>
    // This will get the actual bowler from the list
    public Bowler getItemAtPosition(int position) {
        return bowlersList.get(position);
    }

    //Formatting TimeStamp to 'EEE MMM dd yyyy (HH:mm:ss)'
    //Input  : 2018-05-23 9:59:01
    //Output : Wed May 23 2018 (9:59:01)
    private String formatDate(String dateStr) {
        try {
            SimpleDateFormat fmt = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
            Date date = fmt.parse(dateStr);
            SimpleDateFormat fmtOut = new SimpleDateFormat("EEE MMM dd yyyy (HH:mm:ss)");
            return fmtOut.format(date);
        } catch (ParseException e) {
        }
        return "";
    }

    public void notifyDatasetChanged(List<Bowler> newbowlerlist) {
        bowlersList.clear();
        bowlersList.addAll(newbowlerlist);
        super.notifyDataSetChanged();
    }
}
  • Note the notifyDatasetChanged(List<Bowler) method, this takes the changed bowler list and rebuilds the adapter's copy that it works with and then calls the adapter's notfiydatasetChanged method.

  • Added but not used is the getItemAtPosition method that can be used to return the bowler from the adapter at the given position (this could be used to circumvent altering the activity's copy of the bowlerlist).

DatabaseHelper

This just includes some changes that log's some information but is otherwise unchanged (although some missing code was added).

public class DatabaseHelper extends SQLiteOpenHelper {

    public static final String DBNAME = "leagueapp.db";
    public static final int DBVERSION = 1;

    SQLiteDatabase mDB;

    public DatabaseHelper(Context context) {
        super(context, DBNAME, null, DBVERSION);
        mDB = this.getWritableDatabase();
    }

    @Override
    public void onCreate(SQLiteDatabase db) {
        db.execSQL(Bowler.CRTSQL);
    }

    @Override
    public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {

    }

    public long insertBowler(String leagueId, String bowlerName) {
        //Get Writable Database That We Want To Write Data Too
        SQLiteDatabase db = this.getWritableDatabase();

        Log.d("INSERTBOWLER","Number of bowlers in db = " + String.valueOf(DatabaseUtils.queryNumEntries(db,Bowler.TABLE_NAME)));

        ContentValues values = new ContentValues();
        //`id` and `timestamp` Will Be Inserted Automatically
        values.put(Bowler.COLUMN_LEAGUE_ID, leagueId);
        values.put(Bowler.COLUMN_NAME, bowlerName);

        //Insert Row
        long id = db.insertOrThrow( Bowler.TABLE_NAME, null, values );

        //Close Database Connection
        db.close();

        //Return Newly Inserted Row Id
        return id;
    }

    public Bowler getBowler(String leagueId) {
        //Get Readable Database If We Are Not Inserting Anything
        SQLiteDatabase db = this.getReadableDatabase();

        Cursor cursor = db.query(Bowler.TABLE_NAME,
                new String[]{Bowler.COLUMN_ID, Bowler.COLUMN_LEAGUE_ID, Bowler.COLUMN_NAME, Bowler.COLUMN_TIMESTAMP},
                Bowler.COLUMN_LEAGUE_ID + "=?",
                new String[]{String.valueOf(leagueId)}, null, null, null, null);

        if (cursor.moveToFirst()) {

            //Prepare Bowler Object
            Bowler bowler = new Bowler(
                    cursor.getLong(cursor.getColumnIndex(Bowler.COLUMN_ID)),
                    cursor.getString(cursor.getColumnIndex(Bowler.COLUMN_LEAGUE_ID)),
                    cursor.getString(cursor.getColumnIndex(Bowler.COLUMN_NAME)),
                    cursor.getString(cursor.getColumnIndex(Bowler.COLUMN_TIMESTAMP)));

            //Close Database Connection
            cursor.close();
            return bowler;
        } else {return null;}
    }
    public List<Bowler> getAllBowlers(String leagueId) {
        List<Bowler> bowlers = new ArrayList<>();

        //Select All Query
        String selectQuery = "SELECT  * FROM " + Bowler.TABLE_NAME + " WHERE " + Bowler.COLUMN_LEAGUE_ID + " = '" + leagueId + "'" + " ORDER BY " +
                Bowler.COLUMN_TIMESTAMP + " DESC";

        Log.d("GETALLBOWLERS-SQL","SQL used = >>>>" +selectQuery + "<<<<");

        SQLiteDatabase db = this.getWritableDatabase();
        Cursor cursor = db.rawQuery(selectQuery, null);

        Log.d("GETALLBOWLERS-CNT","Number of rows retrieved = " + String.valueOf(cursor.getCount()));


        //Looping Through All Rows And Adding To The List
        if (cursor.moveToFirst()) {
            do {
                Bowler bowler = new Bowler();
                bowler.setId(cursor.getInt(cursor.getColumnIndex(Bowler.COLUMN_ID)));
                bowler.setLeagueId(cursor.getString(cursor.getColumnIndex(Bowler.COLUMN_LEAGUE_ID)));
                bowler.setName(cursor.getString(cursor.getColumnIndex(Bowler.COLUMN_NAME)));
                bowler.setTimestamp(cursor.getString(cursor.getColumnIndex(Bowler.COLUMN_TIMESTAMP)));
                bowlers.add(bowler);
            } while (cursor.moveToNext());
        }
        cursor.close();
        //Close Database Connection
        db.close();
        Log.d("GETALLBOWLERS-CNT","Number of elements in bowlerslist = " + String.valueOf(bowlers.size()));

        //Return Bowlers List
        return bowlers;
    }

    public int getBowlersCount() {
        String countQuery = "SELECT  * FROM " + Bowler.TABLE_NAME;
        SQLiteDatabase db = this.getReadableDatabase();
        Cursor cursor = db.rawQuery(countQuery, null);

        int count = cursor.getCount();
        cursor.close();

        //Return The Count
        return count;
    }

    public int updateBowler(Bowler bowler) {
        SQLiteDatabase db = this.getWritableDatabase();

        ContentValues values = new ContentValues();
        values.put(Bowler.COLUMN_LEAGUE_ID, bowler.getLeagueId());
        values.put(Bowler.COLUMN_NAME, bowler.getName());

        //Updating Row
        return db.update(Bowler.TABLE_NAME, values, Bowler.COLUMN_ID + " = ?",
                new String[]{String.valueOf(bowler.getId())});
    }

    public void deleteBowler(Bowler bowler) {
        SQLiteDatabase db = this.getWritableDatabase();
        db.delete( Bowler.TABLE_NAME, Bowler.COLUMN_ID + " = ?",
                new String[]{String.valueOf( bowler.getId())});
        db.close();
    }

    public int deleteBowlerChecked(Bowler bowler) {
        SQLiteDatabase db = this.getWritableDatabase();
        Log.d("DELETEBOWLER","Attempting to DELETE bowler " + bowler.getName());
        int rv = db.delete(Bowler.TABLE_NAME,Bowler.COLUMN_ID + "=?",
                new String[]{String.valueOf(bowler.getId())});
        if (rv < 1) {
            Log.d("DELETEBOWLER", "Bowler with an id of " + String.valueOf(bowler.getId()) + " was not deleted, as it didn't exist.");
        }
        return rv;
    }
}

RecyclerTouchListener

This wad added, it may or may not be the same as yours.

public class RecyclerTouchListener implements RecyclerView.OnItemTouchListener{

    private BowlerActivity.ClickListener clicklistener;
    private GestureDetector gestureDetector;

    public RecyclerTouchListener(Context context, final RecyclerView recycleView, final BowlerActivity.ClickListener clicklistener){

        this.clicklistener=clicklistener;
        gestureDetector=new GestureDetector(context,new GestureDetector.SimpleOnGestureListener(){
            @Override
            public boolean onSingleTapUp(MotionEvent e) {
                return true;
            }

            @Override
            public void onLongPress(MotionEvent e) {
                View child=recycleView.findChildViewUnder(e.getX(),e.getY());
                if(child!=null && clicklistener!=null){
                    clicklistener.onLongClick(child,recycleView.getChildAdapterPosition(child));
                }
            }
        });
    }

    @Override
    public boolean onInterceptTouchEvent(RecyclerView rv, MotionEvent e) {
        View child=rv.findChildViewUnder(e.getX(),e.getY());
        if(child!=null && clicklistener!=null && gestureDetector.onTouchEvent(e)){
            clicklistener.onClick(child,rv.getChildAdapterPosition(child));
        }

        return false;
    }

    @Override
    public void onTouchEvent(RecyclerView rv, MotionEvent e) {

    }

    @Override
    public void onRequestDisallowInterceptTouchEvent(boolean disallowIntercept) {

    }

}

Results/Testing

Initial Screen (empty db) :-

enter image description here

Add Bowler Button clicked :-

enter image description here

Bowler B1 entered and Save clicked :-

enter image description here

More Bowlers added :-

enter image description here

Bowler B5 updated :-

enter image description here

Bowlers B1 and B6 deleted :-

enter image description here

MikeT
  • 51,415
  • 16
  • 49
  • 68
  • I have amended my code as you showed above, I am however not getting the same results as your screenshot. When I click the save button and it goes back to the Bowler Listview the activity is totally empty, I have added screenshots and revised code above. – Robert Vogl Jun 07 '18 at 12:26
  • I believe I have isolated the issue in the .notifyDataSetChanged(bowlerList) I am calling bowlersList.clear();. If I comment this out I am back to the listview refreshing and using the very first bowler name until the activity is refreshed. When I uncomment this the activity gets refreshed and displays nothing like the screen shot I post above. – Robert Vogl Jun 07 '18 at 13:29
  • I am wondering if this is a result of me only displaying records that match a certain criteria. For example If League Id =1 the bowlersList is displaying only Bowlers that belong to the League with that id. The reason that I mention this is that when I add a new League to the list this issue is not present. There is no filtering or displaying by an id number for the League. – Robert Vogl Jun 07 '18 at 15:27
  • @RobertVogl try without the filter (selection criteria) if that resolves it then check the filtering. perhaps use a breakpoint, say at bowlerlist.clear() run with debug, check to see what newbowlerlist contains. alternately add Log statements and check the log. – MikeT Jun 07 '18 at 19:53
  • I added a break point at .clear() and I can clearly see the new bowlers name, but Bowler n = B1 (the first bowler entered). Also the ArrayList size = 0 once it get to the .notifyDataSetChanged(). I am sure this should be greater than 0, other wise it can't refresh the list, if I am not mistaken. Log is above. – Robert Vogl Jun 07 '18 at 20:18
  • Did you also see `bowlersList = {ArrayList@5037} size = 0`, the bowlerName you see is not from within the ArrayList. It's the ArrayList that contains the data that will be displayed. – MikeT Jun 07 '18 at 20:25
  • Yes, this was what I was referring to with ArrayLlist size = 0. The name that I see has already been written to the database, correct? So why wouldn't the adapter be refreshing itself once it has been notified of the data change, isn't that what .notifyDataSetChanged() suppose to do. By the way I removed the filtering and the results where identical. – Robert Vogl Jun 07 '18 at 20:39
  • I suspect that it may well be that bowlers are not being added to the DB. Add line `Log.d("INSERTBOWLER","Number of bowlers in db = " + String.valueOf(DatabaseUtils.queryNumEntries(db,Bowler.TABLE_NAME)));` in the `insertBowler` method and also temporarily change to use `long id = db.insertOrThrow( Bowler.TABLE_NAME, null, values );`. Run, add bowler and check the log. – MikeT Jun 07 '18 at 20:58
  • see log file snippet above. The log.d shows the bowler count going up. The second line of code does nothing that I can see. – Robert Vogl Jun 08 '18 at 17:02
  • OK, insert is happening OK (you can remove the changes). Now make the following changes to the `getAllBowlers` method (1 write the selectQuery sql to the log, 2 write the number of rows retrieved to the log and 3 write the size of the bowlers ArrayList to the log). Run and report or fix. **Note the answer has been edited to so base the changes on the answer.** – MikeT Jun 08 '18 at 21:06
  • I used your .notifyDataSetChanged call (mAdapter.notifyDatasetChanged(db.getAllBowlers(savedLeagueId));) in my code and the list refreshes. So you are correct it was the way I was implementing .notifyDataSetChanged(). My code was correct while I was not filtering by any FK from another table in my LeagueActivity. However as you showed in your testing code above, I need to pass a value for the .notifiyDataSetChanged to check if there was a change. Thank you for all your efforts on this one. I would not have found this on my own. – Robert Vogl Jun 09 '18 at 10:23