Logical pattern for listeners/observers to implement RecyclerView list and details activities

て烟熏妆下的殇ゞ 提交于 2019-12-06 12:13:06

One of the easiest ways to manage your database from multiple locations in an app is to use a ContentProvider and designate content:// URI's for each table in your database.

To maintain the "master" list view:

  • Suppose you have a database table called "animals" and the URI to access the table via the ContentProvider is content://myPackage/animals. For your RecyclerView activity, in onCreate you would start a CursorLoader on the content://myPackage/animals URI.
  • Assuming you design your ContentProvider correctly (ie. insert, delete and update calls end with ContentProvider.notifyChange()), your loader will automatically query and requery the database any time the table changes. From the loader's onLoadFinished() callback, you take the cursor it returns and update your recycler view adapter with it. Although a somewhat simplified explanation, this is pretty much the essentials to keep the master list updated even as changes to the database are made in other parts of the app.

To handle clicks/long clicks in the list:

  • There is not necessarily a "best way" to do this, but in the adapter's onCreateViewHolder() method I usually take the base view for an item and set the ViewHolder containing the view as its on click listener. This is because when the item is clicked, the ViewHolder knows important information about where it is within the list (using getAdapterPosition() or getItemId()).
  • If for example the user long clicked an item with an ID of 3, I would update the database using ContentResolver.update() and a URI of content://mypackage/animals/3. After the update was successful, the master list would then automatically requery the database and refresh the list with the new state for the item with ID #3.
Dale

Here is one way to implement this set of functionality. All of the functionality described in the original question is implemented and is available here: Sample Application on GitHub. Also, if you have suggestions for improvement, please advise, either here or on GitHub.

Generally, there is a model held in the list activity, and that model is passed as an extra into the slide activity. Changes to the slide activity model are easily reflected there and in the database, and a bit of effort was required to allow the changes in the slide activity to appear in the list activity when the user pressed the back button.

The main activity onCreate creates an SQLite database adapter and puts some data in there. It creates an ArrayList<Model> and uses that to build the RecyclerView.Adapter:

public class RecyclerSqlListActivity extends AppCompatActivity {
    ....    
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_recycler_list);

        recyclerView = (RecyclerView) findViewById(R.id.recyclerView1);
        recyclerView.setLayoutManager(new LinearLayoutManager(this));

        try {
            repository = new DbSqlliteAdapter(this);
            repository.open();
            //repository.deleteAll();
            //repository.insertSome();

            modelList = repository.loadModelFromDatabase();// <<< This is select * from table into the modelList
            Log.v(TAG, "The modelList has " + modelList.size() + " entries.");

            recyclerViewAdapter = new MyRecyclerViewAdapter(this, modelList);
            recyclerView.setAdapter(recyclerViewAdapter);
            recyclerView.hasFixedSize();

        } catch (Exception e) {
            Log.v(TAG, "Exception in onCreate " + e.getMessage());
        }
    }
...
}

Here's the layout:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
              android:layout_width="fill_parent" android:layout_height="fill_parent"
              android:orientation="vertical">

    <android.support.v7.widget.RecyclerView
        android:id="@+id/recyclerView1"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:scrollbars="vertical" />

</LinearLayout>

The RecyclerView.Adapter is where the listeners are. I listen for a click and a long click. The click starts the slide activity and the long click changes the database so that the icon shows or not. When the user takes action that changes data, I need to make sure the model, and database get updated and I need to notify the view that something has changed. Note that this implementation just manages things that have actually changed, as opposed to refreshing everything.

class MyRecyclerViewAdapter extends RecyclerView.Adapter<MyRecyclerViewAdapter.ViewHolder> {

    static class ViewHolder extends RecyclerView.ViewHolder {
        ImageView iconImageView;
        TextView nameTextView;
        TextView secondLineTextView;
        TextView dbItemTextView;
        TextView hiddenTextView;
        TextView descriptionTextView;

        ViewHolder(View itemView) {
            super(itemView);

            iconImageView = (ImageView) itemView.findViewById(R.id.bIcon);
            nameTextView = (TextView) itemView.findViewById(R.id.bName);
            secondLineTextView = (TextView) itemView.findViewById(R.id.bSecondLine);
            dbItemTextView = (TextView) itemView.findViewById(R.id.bDbItem);
            hiddenTextView = (TextView) itemView.findViewById(R.id.bHidden);
            descriptionTextView = (TextView) itemView.findViewById(R.id.bDescription);
        }
    }
    private List<Model> mModelList;
    private Context mContext;

    MyRecyclerViewAdapter(Context context, List<Model> modelList) {
        mContext = context;
        mModelList = new ArrayList<>(modelList);
    }

    @Override
    public MyRecyclerViewAdapter.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        Context context = parent.getContext();
        LayoutInflater inflater = LayoutInflater.from(context);
        View modelView = inflater.inflate(R.layout.list_item, parent, false);
        return new ViewHolder(modelView);
    }

    @Override
    public void onBindViewHolder(ViewHolder viewHolder, int position) {
        final Model model = mModelList.get(position);

        viewHolder.nameTextView.setText(model.getName());
        viewHolder.secondLineTextView.setText(model.getSecond_line());
        viewHolder.dbItemTextView.setText(model.getId() + "");
        viewHolder.hiddenTextView.setText(model.getHidden());
        viewHolder.descriptionTextView.setText(model.getDescription());

        if ("F".equals(model.getHidden())) {
            viewHolder.secondLineTextView.setVisibility(View.VISIBLE);
            viewHolder.iconImageView.setVisibility(View.INVISIBLE);
        } else {
            viewHolder.secondLineTextView.setVisibility(View.INVISIBLE);
            viewHolder.iconImageView.setVisibility(View.VISIBLE);
        }

        // DEFINE ACTIVITY THAT HAPPENS WHEN ITEM IS CLICKED
        viewHolder.itemView.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                Log.v(TAG, "setOnClickListener fired with view " + view); // view is RelativeLayout from list_item.xml

                int position = mModelList.indexOf(model);
                Log.v(TAG, "position was : "  + position);

                Intent intent = new Intent(mContext, DetailSlideActivity.class);
                intent.putExtra(DetailSlideActivity.EXTRA_LIST_MODEL, (Serializable)mModelList);
                intent.putExtra(DetailSlideActivity.EXTRA_POSITION, position);
                ((Activity)mContext).startActivityForResult(intent, RecyclerSqlListActivity.DETAIL_REQUEST);
            }
        });

        // If the item is long-clicked, we want to change the icon in the model and in the database
        viewHolder.itemView.setOnLongClickListener(new View.OnLongClickListener() {
            @Override
            public boolean onLongClick(View view) {
                Log.v(TAG, "setOnLongClickListener fired with view " + view); // view is RelativeLayout from list_item.xml
                Log.v(TAG, "setOnLongClickListener getTag method gave us position: " + view.getTag());

                int position = mModelList.indexOf(model);
                Log.v(TAG, "position was : "  + position);

                String hidden = model.getHidden();
                Log.v(TAG, "hidden string was : "  + hidden);


                if ("F".equals(hidden)) {
                    model.setHidden("T");
                    DbSqlliteAdapter.update(model);
                    view.findViewById(R.id.bIcon).setVisibility(View.INVISIBLE);
                } else {
                    model.setHidden("F");
                    view.findViewById(R.id.bIcon).setVisibility(View.VISIBLE);
                }
                Log.v(TAG, "updating the database");
                DbSqlliteAdapter.update(model);
                Log.v(TAG, "notifyItemChanged being called");
                notifyItemChanged(position);

                boolean longClickConsumed = true;  // no more will happen :)
                return longClickConsumed;
            }
        });
    }

Rather than put all of the code here, I'll just put a subset, and more details can be found by going to the github link, above. But one last comment on how the app was designed to get the list of items that were altered during the slide activity. Each time a change happened in the slide activity, the position was saved at the same time the database was updated. Then, when the slide activity ended, onActivityResult() pulled all of those positions and updated the model that is held in the original activity.

public class RecyclerSqlListActivity extends AppCompatActivity {
    ....
    @Override
    public void onActivityResult(int requestCode, int resultCode, Intent intent) {
        if(requestCode == RecyclerSqlListActivity.DETAIL_REQUEST){
            Log.v(TAG, "onActivityResult fired <<<<<<<<<< resultCode:" + resultCode);
            //String[] changedItems = intent.getStringArrayExtra(RecyclerSqlListActivity.DETAIL_RESULTS); // We could return things we learned, such as which items were altered.  Or we could just update everything
            //modelList = repository.loadModelFromDatabase();// <<< This is select * from table into the modelList
            Integer[] changedPositions = DbSqlliteAdapter.getChangedPositions();
            for (Integer changedPosition : changedPositions) {
                Model aModel = modelList.get(changedPosition);
                DbSqlliteAdapter.loadModel(aModel);
                recyclerViewAdapter.notifyItemChanged(changedPosition);
            }
        }
    }
    ....
}

Here are a few of the classes that interact with the database. I decided against ContentProvider because, according to the documentation, that's for sharing data between applications, not within one application.

class DbSqlliteAdapter {
    ....    
    static Model loadModel(Model model) {
        Model dbModel = DbSqlliteAdapter.getById(model.getId() + "");
        Log.v(TAG, "looked up " + model.getId() + " and it found " + dbModel.toString());
        model.setId(dbModel.getId());
        model.setName(dbModel.getName());
        model.setSecond_line(dbModel.getSecond_line());
        model.setDescription(dbModel.getDescription());
        model.setHidden(dbModel.getHidden());
        return model;
    }

    private static class DatabaseHelper extends SQLiteOpenHelper {....}

    ....

    static void update(Model model) {
        ContentValues values = fillModelValues(model);
        Log.v(TAG, "Working on model that looks like this: " + model);
        Log.v(TAG, "Updating record " + values.get(Model.ID) + " in the database.");
        mDb.update(SQLITE_TABLE, values, Model.ID + "=?", new String[] {"" + values.get(Model.ID)});
        Model resultInDb = getById("" + values.get(Model.ID));
        Log.v(TAG, "after update, resultInDb: " + resultInDb);
    }

    static void update(Model model, int position) {
        positionSet.add(position);
        update(model);
    }

    static Integer[] getChangedPositions() {
        Integer[] positions = positionSet.toArray(new Integer[0]);
        positionSet.clear();
        return positions;
    }
    ....    
}

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!