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





5.00/5 (1投票)
本文介绍了如何使用 Room 和 RecyclerView,以及 ViewModel 在 Android 中操作数据库。
引言
本文解决了一些常见任务
- 在应用程序中存储数据 - 使用 Room
- 向用户展示数据 - 使用 Fragment 和 RecyclerView
- 使用
ViewModel
存储和自动更新数据
背景
Room 在 SQLite 之上提供了一个抽象层,允许流畅地访问数据库,同时利用 SQLite 的全部功能。应用程序使用 Room
数据库来获取与该数据库关联的数据访问对象 (DAO)。然后,应用程序使用每个 DAO 从数据库获取实体,并将这些实体的任何更改保存回数据库。最后,应用程序使用实体来获取和设置与数据库中表列相对应的值。
在 RecyclerView
小部件中,有几个不同的组件协同工作以显示您的数据(对象列表)。用户界面的整体容器是您添加到布局中的 RecyclerView
对象。RecyclerView
会用您提供的布局管理器提供的视图来填充自身。您可以使用我们的标准布局管理器之一(例如 LinearLayoutManager
或 GridLayoutManager
),或者实现自己的布局管理器。
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>
并将 TextView
的 background
值设置为 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>
让我们进入数据库。此应用程序侧重于组件的子集,即 LiveData
、ViewModel
和 Room
。此图显示了此架构的基本形式。您可以在 此处 阅读更多相关信息。
在应用程序模块的 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
必须是 interface
或 abstract
类。默认情况下,所有查询都必须在单独的线程上执行。好的,创建我们自己的 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 数据与 Activity
和 Fragment
类分开,可以更好地遵循单一职责原则:您的 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");
}
}
}
希望这篇简单的文章能帮到您。您可以轻松地改进这个应用程序。我喜欢开发应用程序,所以您可以 在这里 尝试其中一些。