65.9K
CodeProject 正在变化。 阅读更多。
Home

在 Android 应用程序中使用 Room 和 RecyclerView 操作数据库

starIconstarIconstarIconstarIconstarIcon

5.00/5 (1投票)

2019 年 2 月 23 日

CPOL

5分钟阅读

viewsIcon

39718

downloadIcon

2660

本文介绍了如何使用 Room 和 RecyclerView,以及 ViewModel 在 Android 中操作数据库。

引言

本文解决了一些常见任务

  • 在应用程序中存储数据 - 使用 Room
  • 向用户展示数据 - 使用 Fragment 和 RecyclerView
  • 使用 ViewModel 存储和自动更新数据

背景

Room 在 SQLite 之上提供了一个抽象层,允许流畅地访问数据库,同时利用 SQLite 的全部功能。应用程序使用 Room 数据库来获取与该数据库关联的数据访问对象 (DAO)。然后,应用程序使用每个 DAO 从数据库获取实体,并将这些实体的任何更改保存回数据库。最后,应用程序使用实体来获取和设置与数据库中表列相对应的值。

RecyclerView 小部件中,有几个不同的组件协同工作以显示您的数据(对象列表)。用户界面的整体容器是您添加到布局中的 RecyclerView 对象。RecyclerView 会用您提供的布局管理器提供的视图来填充自身。您可以使用我们的标准布局管理器之一(例如 LinearLayoutManagerGridLayoutManager),或者实现自己的布局管理器。

ViewModel 是一个类,负责准备和管理 Activity 或 Fragment 的数据。它还处理 Activity / Fragment 与应用程序其余部分的通信。换句话说,这意味着如果所有者的配置发生更改(例如旋转),ViewModel 不会被销毁。所有者的新实例将重新连接到现有的 ViewModel

Using the Code

我们开始吧!在 Android Studio 中创建一个新项目(我使用的是 3.2.1 版本),选择 empty activity,或者您可以下载源文件并选择:File-New-Import project。我们将构建一个这样的应用程序

您可以根据需要向数据库添加和删除数据,并在屏幕上显示它们。

我们需要一个数据类 DataItem

public class DataItem {
    private long id;
    private String name;
    private String content;
    private String details;
    private String section;
}

这是我们要在数据库中存储的数据类。要显示这些数据,我们使用 RecyclerView 小部件。创建一个新的 Fragment:File-New-Fragment-Fragment(list)。RecyclerView 使用两个 XML 文件:一个文件表示列表项,第二个文件表示完整的列表项。对 fragment_item 进行一些更改:添加 CardView 小部件并创建一个自定义 TextView 元素。另外,将 CardView 小部件添加到 build.gradle

//for recyclerview items
implementation 'com.android.support:cardview-v7:28.0.0'

Fragment_item.xml:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:orientation="vertical">

    <android.support.v7.widget.CardView
        android:id="@+id/card_view"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"

        android:layout_margin="2dp"
        app:cardCornerRadius="2dp"
        app:cardElevation="3dp"
        app:cardUseCompatPadding="true">

        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_gravity="center"
            android:orientation="horizontal">

            <TextView
                android:id="@+id/item_number"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_margin="@dimen/text_margin"
                android:background="@drawable/round_shape"
                android:gravity="center"
                android:text="1"
                android:textAppearance="?attr/textAppearanceListItem"
                android:textColor="@color/colorWhite" />

            <TextView
                android:id="@+id/item_name"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_margin="@dimen/text_margin"
                android:text="text"
                android:textAppearance="?attr/textAppearanceListItem" />

            <LinearLayout
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:layout_gravity="center"
                android:gravity="end"
                android:orientation="horizontal">

                <TextView
                    android:id="@+id/item_section"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:layout_gravity="center"
                    android:layout_margin="@dimen/text_margin"
                    android:text="section"
                    android:textAppearance="?attr/textAppearanceListItem" />

                <ImageView
                    android:id="@+id/image_delete"
                    android:layout_width="40dp"
                    android:layout_height="40dp"
                    android:padding="8dp"
                    android:src="@drawable/ic_delete2" />
            </LinearLayout>

        </LinearLayout>
    </android.support.v7.widget.CardView>
</LinearLayout>

要创建一个红色的圆形 TextView 并显示数字,我们需要 round_shape.xml

<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
    android:shape="oval">
    <solid android:color="#FF0000" />
    <size
        android:width="30dp"
        android:height="30dp" />
</shape>

并将 TextViewbackground 值设置为 round_shape.xml

android:background="@drawable/round_shape"

fragment_list.xml 中添加一个按钮

<?xml version="1.0" encoding="utf-8"?>

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="vertical">

        <Button
            android:id="@+id/add_button"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_gravity="end"
            android:layout_margin="@dimen/list_margin"
            android:text="Add" />
    </LinearLayout>

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="vertical">

        <android.support.v7.widget.RecyclerView
            android:id="@+id/list"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_marginLeft="@dimen/list_margin"
            android:layout_marginRight="@dimen/list_margin"
            app:layoutManager="android.support.v7.widget.LinearLayoutManager"
            tools:context=".fragment.ListFragment"
            tools:listitem="@layout/fragment_item" />
    </LinearLayout>
</LinearLayout>

让我们进入数据库。此应用程序侧重于组件的子集,即 LiveDataViewModelRoom。此图显示了此架构的基本形式。您可以在 此处 阅读更多相关信息。

在应用程序模块的 build.gradle 中添加一些内容

// Room components
    implementation "android.arch.persistence.room:runtime:1.1.1"
    annotationProcessor "android.arch.persistence.room:compiler:1.1.1"
    androidTestImplementation "android.arch.persistence.room:testing:1.1.1"

// Lifecycle components
    implementation "android.arch.lifecycle:extensions:1.1.1"
    annotationProcessor "android.arch.lifecycle:compiler:1.1.1"

为了让 DataItem 类对 Room 数据库有意义,您需要对其进行注释。注释标识此类中的每个部分如何与数据库中的条目相关联。Room 使用此信息来生成代码。

  • @Entity(tableName = "data_item_table")
    每个 @Entity 类代表一个表中的一个实体。注释您的类声明以表明它是一个实体。如果您希望表名与类名不同,请指定表名。
  • @PrimaryKey
    每个实体都需要一个主键。为了简单起见,每个单词都充当自己的主键。
  • @NonNull
    表示参数、字段或方法返回值永远不能为 null
  • @ColumnInfo(name = "name")
    如果您希望列名与成员变量名不同,请指定表中的列名。
  • 数据库中存储的每个字段都需要是 public 的,或者有一个“getter”方法。此示例提供了一个 geName() 方法。

将我们的类更改为这样

@Entity
public class DataItem {
    @PrimaryKey(autoGenerate = true)
    @NonNull
    private long id;
    private String name;
    private String content;
    private String details;
    private String section;

    @Ignore
    public DataItem(String name, String content, String details, String section) {
        this.name = name;
        this.content = content;
        this.details = details;
        this.section = section;
    }

    public void setId(long id) {
        this.id = id;
    }

    public long getId() {
        return id;
    }

    public String getName() {
        return name;
    }

    public String getContent() {
        return content;
    }

    public String getDetails() {
        return details;
    }

    public void setName(String name) {
        this.name = name;
    }

    public void setContent(String content) {
        this.content = content;
    }

    public void setDetails(String details) {
        this.details = details;
    }

    public String getSection() {
        return section;
    }

    public void setSection(String section) {
        this.section = section;
    }

    public DataItem() {
        this.name = "name";
        this.content = "content";
        this.details = "details";
        this.section = "section";
    }
}

DAO(数据访问对象)中,您指定 SQL 查询并将其与方法调用关联起来。编译器会检查 SQL 并从方便的注释(如 @Insert@Delete@Query)生成查询。DAO 必须是 interfaceabstract 类。默认情况下,所有查询都必须在单独的线程上执行。好的,创建我们自己的 DAO interface

@Dao
public interface DataDAO {

    //Insert one item
    @Insert(onConflict = IGNORE)
    void insertItem(DataItem item);

    // Delete one item
    @Delete
    void deleteItem(DataItem person);

    //Delete one item by id
    @Query("DELETE FROM dataitem WHERE id = :itemId")
    void deleteByItemId(long itemId);

    //Get all items
    @Query("SELECT * FROM DataItem")
    LiveData<List<DataItem>> getAllData();

    //Delete All
    @Query("DELETE FROM DataItem")
    void deleteAll();
}

当数据发生更改时,您通常需要采取一些操作,例如在 UI 中显示更新的数据。这意味着您必须观察数据,以便在数据发生更改时做出反应。LiveData,一个生命周期库中用于数据观察的类,解决了这个问题。在方法描述中使用 LiveData 类型作为返回值,Room 会生成所有必要的代码,以便在数据库更新时更新 LiveData

接下来,创建基于 RoomDatabase 的数据库类

@Database(entities = {DataItem.class}, version = 1, exportSchema = false)
public abstract class DataRoomDbase extends RoomDatabase {

    private static DataRoomDbase INSTANCE;

    public abstract DataDAO dataDAO();

    public static DataRoomDbase getDatabase(Context context) {
        if (INSTANCE == null) {
            INSTANCE = Room.databaseBuilder(context.getApplicationContext(), 
                       DataRoomDbase.class, DataRoomDbase.class.getName())
                    //if you want create db only in memory, not in file
                    //Room.inMemoryDatabaseBuilder
                    //(context.getApplicationContext(), DataRoomDbase.class)
                    .build();
        }
        return INSTANCE;
    }

    public static void destroyInstance() {
        INSTANCE = null;
    }
}

下一步,创建 Repository。Repository 是一个抽象化对多个数据源访问的类。Repository 不是 Architecture Components 库的一部分,但它是代码分离和架构的建议最佳实践。Repository 类负责数据操作。它为应用程序的其余部分提供了一个干净的数据 API。像这样创建

public class DataRepository {
    private DataDAO mDataDao;
    private LiveData<List<DataItem>> mAllData;

    public DataRepository(Application application) {
        DataRoomDbase dataRoombase = DataRoomDbase.getDatabase(application);
        this.mDataDao = dataRoombase.dataDAO();
        this.mAllData = mDataDao.getAllData();
    }

    LiveData<List<DataItem>> getAllData() {
        return mAllData;
    }

// You must call this on a non-UI thread or your app will crash

    public void insert(DataItem dataItem) {
        new insertAsyncTask(mDataDao).execute(dataItem);
    }

    private static class insertAsyncTask extends AsyncTask<DataItem, Void, Void> {
        private DataDAO mAsyncTaskDao;
        insertAsyncTask(DataDAO dao) {
            mAsyncTaskDao = dao;
        }

        @Override
        protected Void doInBackground(final DataItem... params) {
            mAsyncTaskDao.insertItem(params[0]);
            return null;
        }
    }

    public void deleteItem(DataItem dataItem) {
        new deleteAsyncTask(mDataDao).execute(dataItem);
    }

    private static class deleteAsyncTask extends AsyncTask<DataItem, Void, Void> {
        private DataDAO mAsyncTaskDao;
        deleteAsyncTask(DataDAO dao) {
            mAsyncTaskDao = dao;
        }

        @Override
        protected Void doInBackground(final DataItem... params) {
            mAsyncTaskDao.deleteItem(params[0]);
            return null;
        }
    }

    public void deleteItemById(Long idItem) {
        new deleteByIdAsyncTask(mDataDao).execute(idItem);
    }

    private static class deleteByIdAsyncTask extends AsyncTask<Long, Void, Void> {
        private DataDAO mAsyncTaskDao;
        deleteByIdAsyncTask(DataDAO dao) {
            mAsyncTaskDao = dao;
        }

        @Override
        protected Void doInBackground(final Long... params) {
            mAsyncTaskDao.deleteByItemId(params[0]);
            return null;
        }
    }
}

创建一个基于 ViewModel 类的类

public class DataViewModel extends AndroidViewModel {

    private DataRepository mDataRepository;
    private LiveData<List<DataItem>> mListLiveData;

    public DataViewModel(@NonNull Application application) {
        super(application);
        mDataRepository = new DataRepository((application));
        mListLiveData = mDataRepository.getAllData();
    }

    public LiveData<List<DataItem>> getAllData() {
        return mListLiveData;
    }

    public void insertItem(DataItem dataItem) {
        mDataRepository.insert(dataItem);
    }

    public void deleteItem(DataItem dataItem) {
        mDataRepository.deleteItem(dataItem);
    }

    public void deleteItemById(Long idItem) {
        mDataRepository.deleteItemById(idItem);
    }
}

ViewModel 以一种可以跨配置更改生存的生命周期方式保存您的应用程序的 UI 数据。将应用程序的 UI 数据与 ActivityFragment 类分开,可以更好地遵循单一职责原则:您的 Activity 和 Fragment 负责将数据绘制到屏幕上,而您的 ViewModel 可以负责保存和处理 UI 所需的所有数据。

ViewModel 中,使用 LiveData 来处理 UI 将使用或显示的、可变的数据。使用 LiveData 有几个好处

  • 您可以观察数据(而不是轮询更改),并且只在数据实际更改时更新 UI。
  • ViewModel 完全将 Repository 和 UI 分开。ViewModel 中没有数据库调用,使代码更易于测试。
public class MainActivity extends AppCompatActivity 
         implements ListFragment.OnListFragmentInteractionListener,
        AlertDialogFragment.AlertDialogListener {

    private DataViewModel mDataViewModel;

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

        mDataViewModel = ViewModelProviders.of(this).get(DataViewModel.class);
        getSupportFragmentManager()
                .beginTransaction()
                .replace(R.id.main_layout, new ListFragment())
                .addToBackStack("list")
                .commit();
    }

    @Override
    public void onListClickItem(DataItem dataItem) {
        Toast.makeText(this, dataItem.getDetails(), Toast.LENGTH_SHORT).show();
    }

    @Override
    public void onListFragmentDeleteItemById(long idItem) {
        Bundle bundle = new Bundle();
        bundle.putLong(ID_LONG, idItem);

        AlertDialogFragment alertDialogFragment = new AlertDialogFragment();
        alertDialogFragment.setArguments(bundle);
        alertDialogFragment.show(getSupportFragmentManager(), "Allert");
    }

    @Override
    public void onDialogPositiveClick(DialogFragment dialog, long idItem) {
        mDataViewModel.deleteItemById(idItem);
        Toast.makeText(this, getString(R.string.message_delete), Toast.LENGTH_SHORT).show();
    }

    @Override
    public void onDialogNegativeClick(DialogFragment dialog) {
        Toast.makeText(this, getString(R.string.message_cancel), Toast.LENGTH_SHORT).show();

    }
}

使用 ViewModelProviders 将您的 ViewModel 与您的 UI 控制器关联起来。当您的应用程序首次启动时,ViewModelProviders 将创建 ViewModel。当 Activity 被销毁时(例如,由于配置更改),ViewModel 会得以保留。当 Activity 被重新创建时,ViewModelProviders 将返回现有的 ViewModel

public class ListFragment extends Fragment {

    private DataViewModel viewModel;
    private List<DataItem> mDataItemList;
    private ListRecyclerViewAdapter mListAdapter;
    private OnListFragmentInteractionListener mListener;

    public void setListData(List<DataItem> dataItemList) {
        //if data changed, set new list to adapter of recyclerview

        if (mDataItemList == null) {
            mDataItemList = new ArrayList<>();
        }
        mDataItemList.clear();
        mDataItemList.addAll(dataItemList);

        if (mListAdapter != null) {
            mListAdapter.setListData(dataItemList);
        }
    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
                             Bundle savedInstanceState) {
        View view = inflater.inflate(R.layout.fragment_list, container, false);
        Context context = view.getContext();
        //set recyclerview
        RecyclerView recyclerView = view.findViewById(R.id.list);
        recyclerView.setLayoutManager(new LinearLayoutManager(context));
        mListAdapter = new ListRecyclerViewAdapter(mListener);

        if (mDataItemList != null) {
            mListAdapter.setListData(mDataItemList);
        }
        recyclerView.setAdapter(mListAdapter);

        //Add new item to db
        Button addButton = (Button) view.findViewById(R.id.add_button);
        addButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {

                viewModel.insertItem(new DataItem());

            }
        });

        return view;
    }

    @Override
    public void onActivityCreated(@Nullable Bundle savedInstanceState) {
        super.onActivityCreated(savedInstanceState);
        //get viewmodel
        viewModel = ViewModelProviders.of(this).get(DataViewModel.class);
        //bind to Livedata
        viewModel.getAllData().observe(this, new Observer<List<DataItem>>() {
            @Override
            public void onChanged(@Nullable List<DataItem> dataItems) {
                if (dataItems != null) {
                    setListData(dataItems);
                }
            }
        });
    }

    @Override
    public void onAttach(Context context) {
        super.onAttach(context);
        if (context instanceof OnListFragmentInteractionListener) {
            mListener = (OnListFragmentInteractionListener) context;
        } else {
            throw new RuntimeException(context.toString()
                    + " must implement OnListFragmentInteractionListener");
        }
    }

    @Override
    public void onDetach() {
        super.onDetach();
        mListener = null;
    }
    
    public interface OnListFragmentInteractionListener {
        //onClick items of list
        void onListClickItem(DataItem dataItem);

        //onClick delete item from list
        void onListFragmentDeleteItemById(long idItem);
    }
}

另外,当您想从数据库删除数据时,我创建了一个新的类 - AlertDialogFragment(如果您愿意,可以阅读 )。这可以防止内存泄漏,并以一种可以跨配置更改生存的方式使用生命周期。

public class AlertDialogFragment extends DialogFragment {

    AlertDialogListener mListener;
    public static String ID_LONG = "ID_LONG";
    long id_data;

    public interface AlertDialogListener {
        void onDialogPositiveClick(DialogFragment dialog, long idItem);

        void onDialogNegativeClick(DialogFragment dialog);
    }

    @Override
    @NonNull
    public Dialog onCreateDialog(Bundle savedInstanceState) {

        Activity activity = getActivity();
        Bundle bundle = getArguments();

        if (bundle != null && activity != null) {
            //get data from bundle
            id_data = bundle.getLong(ID_LONG);
            // Use the Builder class for convenient dialog construction
            AlertDialog.Builder builder = new AlertDialog.Builder(activity);
            builder.setMessage(R.string.message_dialog)
                    .setPositiveButton(R.string.message_yes, new DialogInterface.OnClickListener() {
                        public void onClick(DialogInterface dialog, int id) {
                            // set listener for yes button
                            mListener.onDialogPositiveClick(AlertDialogFragment.this, id_data);
                        }
                    })
                    .setNegativeButton(R.string.message_no, new DialogInterface.OnClickListener() {
                        public void onClick(DialogInterface dialog, int id) {
                            // User cancelled the dialog
                            mListener.onDialogNegativeClick(AlertDialogFragment.this);
                        }
                    });
            // Create the AlertDialog object and return it
            return builder.create();
        }
        //if no bundle - show error
        AlertDialog.Builder builder = new AlertDialog.Builder(activity)
                .setNegativeButton(R.string.message_error, new DialogInterface.OnClickListener() {

                    @Override
                    public void onClick(DialogInterface dialog, int which) {

                    }
                });
        return builder.create();
    }

    // Override the Fragment.onAttach() method to instantiate the DialogListener

    @Override
    public void onAttach(Context context) {
        super.onAttach(context);
        // Verify that the host activity implements the callback interface
        try {
            // Instantiate the DialogListener so we can send events to the host
            mListener = (AlertDialogListener) context;
        } catch (ClassCastException e) {
            // The activity doesn't implement the interface, throw exception
            throw new ClassCastException(context.toString()
                    + " must implement AlertDialogListener");
        }
    }
}

希望这篇简单的文章能帮到您。您可以轻松地改进这个应用程序。我喜欢开发应用程序,所以您可以 在这里 尝试其中一些。

© . All rights reserved.