【问题标题】:Android: How do I effectively update the SQLite database when dragging and dropping items in the RecyclerView?Android:在 RecyclerView 中拖放项目时如何有效更新 SQLite 数据库?
【发布时间】:2018-07-26 23:35:16
【问题描述】:

我有这个待办事项列表应用程序,其中“任务”可以包含他们自己的“子任务”列表。问题是,每当这些“任务”通过拖放重新排列时,有时会出现奇怪的行为,例如单击它们以显示其子列表时崩溃。

这是拖放监听器的代码:

private void attachItemTouchHelperToAdapter() {
    ItemTouchHelper.SimpleCallback touchCallback =
            new ItemTouchHelper.SimpleCallback(ItemTouchHelper.UP | ItemTouchHelper.DOWN,
                    ItemTouchHelper.LEFT | ItemTouchHelper.RIGHT) {
                @Override
                public boolean onMove(RecyclerView recyclerView,
                                      RecyclerView.ViewHolder viewHolder,
                                      RecyclerView.ViewHolder target) {
                    final int fromPosition = viewHolder.getAdapterPosition();
                    final int toPosition = target.getAdapterPosition();
                    rearrangeTasks(fromPosition, toPosition);

                    // Call background thread to update database.
                    new UpdateDatabaseTask(mTaskList).execute();
                    return true;
                }

                @Override
                public void onSwiped(RecyclerView.ViewHolder viewHolder, int direction) {
                    int position = viewHolder.getAdapterPosition();
                    Task taskToDelete = mTaskAdapter.mTasks.get(position);
                    TaskLab.get(getActivity()).deleteTask(taskToDelete);
                    updateUI(position);
                }
            };

    ItemTouchHelper itemTouchHelper = new ItemTouchHelper(touchCallback);
    itemTouchHelper.attachToRecyclerView(mTaskRecyclerView);
}

重新排列在 onMove 中调用的列表的代码:

private void rearrangeTasks(int fromPosition, int toPosition) {
    if (fromPosition < toPosition) {
        for (int i = fromPosition; i < toPosition; i++) {
            Collections.swap(mTaskList, i, i + 1);
        }
    } else {
        for (int i = fromPosition; i > toPosition; i--) {
            Collections.swap(mTaskList, i, i - 1);
        }
    }
    mTaskAdapter.notifyItemMoved(fromPosition, toPosition);
}

调用更新sqlite数据库的AsyncTask

 private class UpdateDatabaseTask extends AsyncTask<Void, Void, Void> {
    private List<Task> mTaskList;

    public UpdateDatabaseTask(List<Task> taskList) {
        mTaskList = taskList;
    }

    @Override
    protected Void doInBackground(Void... voids) {
        TaskLab.get(getActivity()).updateDatabase(mTaskList);
        return null;
    }

    @Override
    protected void onPostExecute(Void aVoid) {

    }
}

调用数据库辅助函数以使用重新排列的列表更新整个表:

public void updateDatabase(List<Task> tasks) {
    mDatabase.delete(TaskTable.NAME, null, null);
    for (Task task : tasks) {
        addTask(task);
    }
}

这里的问题是,有时,当快速拖放“任务”时,AsyncTask 更新数据库的速度不够快,因此当我尝试在它完成更新之前对其执行功能时,会出现奇怪的行为。

RecyclerView 拖放时更新 SQLite 数据库的更好方法是什么?

【问题讨论】:

    标签: java android sqlite android-asynctask background


    【解决方案1】:

    这个问题太宽泛,无法回答,因为您可以考虑多种解决方案来解决您的问题。我现在可以想到两个,在这里我将简要描述它们。

    解决方案 #1

    您可以考虑直接调用更新表的数据库方法,而不是通过AsyncTask 调用它。在您的数据库表中注册一个内容观察者,以便每次更新表时,RecyclerView 都会随之更新,而无需调用 notifyDataSetChanged()。示例实现应如下所示。

    像这样声明数据库表的 URI。

    public static final Uri DB_TABLE_TASKS_URI = Uri
                .parse("sqlite://" + Constants.ApplicationPackage + "/" + DB_TABLE_TASKS);
    

    现在在您的updateDatabase 函数中,当您的任务表发生更改或更新时,您需要调用此通知函数来通知监听此内容的观察者。

    public static void updateDatabase(List<Task> taskList) {
        // .. Your other implementation goes here 
        context.getContentResolver().notifyChange(DBConstants.DB_TABLE_TASKS_URI, null);
    }
    

    现在使用LoaderCallbacksRecyclerView 中显示sqlite 表中的任务。

    public class TasksActivity extends Activity implements LoaderManager.LoaderCallbacks<Cursor> {
        private final TAKS_QUERY_LOADER = 0;
    
        protected void onCreate(Bundle savedInstanceState) {
            // Other code. 
    
            // Get your cursor loader initialized here. 
            getLoaderManager().initLoader(TASKS_QUERY_LOADER, null, this).forceLoad();
        }
    }
    

    现在您需要在您的TasksActivity 中实现您的LoaderCallbacks 的功能。一个示例实现应如下所示。

    @Override
    public Loader<Cursor> onCreateLoader(int id, Bundle args) {
    
        return new SQLiteCursorLoader(getActivity()) {
            @Override
            public Cursor loadInBackground() {
    
                Cursor cursor;
                cursor = TasksLab.getTasks();
    
                // Register the cursor with the content observer
                this.registerContentObserver(cursor, DBConstants.DB_TABLE_TASKS_URI);
                return cursor;
            }
        };
    }
    
    @Override
    public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
        // Set the cursor in the adapter of your RecyclerView here. 
    }
    
    @Override
    public void onLoaderReset(Loader<Cursor> loader) {
    
    }
    
    // Do not forget to destroy the loader when you are destroying the view.
    @Override
    public void onDestroy() {
        getLoaderManager().destroyLoader(TASKS_QUERY_LOADER);
        super.onDestroyView();
    }
    

    就是这样。您现在可以在您的 sqlite 表中立即更新您的任务,并立即在您的RecyclerView 中获得反映。我强烈建议您一次更新一个任务。如我所见,您正在使用updateDatabase 函数在此处传递整个任务列表来更新数据库表。我建议只传递被拖动或更新的任务 ID,以便表格立即更新。

    解决方案 #2

    此解决方案比前一个解决方案更简单。这个想法是在刷新列表或销毁视图时更新您的 sqlite 数据库。

    当一个任务被拖到某个地方(即更新其位置)或删除时,您需要将轨道保存在您的TasksActivity 中的某个ArrayList 中。不要立即更新数据库中的 sqlite 表,也不要使用 AsyncTask。当您在 onDestroyonPause 方法中销毁视图时,请保存此任务以备后用。在此过程中,每次您的Activity 恢复时(即在onResume 函数中),您都需要刷新您的RecyclerView。所以这里是伪实现。

    @Override
    public boolean onMove(RecyclerView recyclerView,
                          RecyclerView.ViewHolder viewHolder,
                          RecyclerView.ViewHolder target) {
        final int fromPosition = viewHolder.getAdapterPosition();
        final int toPosition = target.getAdapterPosition();
        rearrangeTasks(fromPosition, toPosition);
    
        // Do not call background thread to update database.
        // new UpdateDatabaseTask(mTaskList).execute();
    
        // Keep a local ArrayList and keep the track of dragging the item. 
        updateLocalArrayListWhichIsBindToRecyclerView(mTaskList);
        return true;
    }
    
    @Override
    public void onSwiped(RecyclerView.ViewHolder viewHolder, int direction) {
        int position = viewHolder.getAdapterPosition();
        Task taskToDelete = mTaskAdapter.mTasks.get(position);
        // TaskLab.get(getActivity()).deleteTask(taskToDelete);
        updateLocalArrayListWhichIsBindedToRecyclerView(taskToDelete);
        updateUI(position);
    }
    

    现在,当您离开TaskActivity 时,您最终需要将更改保存在数据库中。

    @Override
    public void onDestroy() {
        updateDatabaseFromTheList();
        super.onDestroyView();
    }
    
    @Override
    public void onPause() {
        updateDatabaseFromTheList();
        super.onDestroyView();
    }
    

    并且每次从onResume 函数内的数据库中加载任务。

    @Override
    public void onResume() {
        super.onResume();
        updateRecyclerViewFromDatabase();
    }
    

    更新

    基于上述评论中解决方案#1 所需的说明。

    “我建议只传递被拖动的任务 ID 或 更新,以便表格立即更新。 ”。我到底该怎么做 这样做?

    我不确定您现在是如何更新任务表的。但是,我可以想到自己的一些实现。只需传递要更新其在任务表中的位置的任务。所以伪实现可能如下所示。

    public class Task {
        public int taskId;
        public int taskPosition; 
    }
    
    public void updateDatabase(Task taskToBeUpdated, int newPosition) {
    
         // You need two update queries. 
         // Update task_table set task_position += task_position + 1 where task_position >= newPosition;
         // Update task_table set task_position = newPosition where taskId = taskTobeUpdated.taskId
    }
    

    希望对您有所帮助。

    【讨论】:

    • 感谢您付出了这么多努力来回答这个问题。您说过,“我建议只传递被拖动或更新的任务 ID,以便表格立即更新。”。我该怎么做?
    猜你喜欢
    • 2020-06-02
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2022-01-10
    • 1970-01-01
    • 2023-01-13
    • 1970-01-01
    相关资源
    最近更新 更多