Android Jetpack架构篇:带视图的Android Room

Android Jetpack架构篇:带视图的Android Room

翻译至:Android Room with a View - Java

1.介绍

架构组件的目的是提供对应用程序体系结构的指导,并为诸如生命周期管理和数据持久化等常见任务提供开发库。

架构组件帮你构造一个鲁棒、易测试、可维护和少模板代码的应用。

架构组件是什么?

为了介绍相关术语,这里有简短的介绍一下各架构组件以及它们之前如何协作。注意这个代码库包含一部分架构组件,它们是:LiveData、ViewModel和Room。每个组件会在使用的时候做解释。下图是基本的架构形式。
[图片上传失败...(image-86c574-1540954630792)]

Entity: 当注解的类,用于描述数据库表。
SQLite database: SQLite数据库。
DAO: 数据访问对象。SQL查询到方法的映射。
Room database: 在SQLite数据库之上的数据库层。
Repository: 数据仓库,用于管理数据源。
ViewModel: 提供数据给UI。是Repository与UI的连接中心。

你要构建什么

这个Demo用于在Room中存储words(单词)列表,并显示在RecyclerView中。这是个简单的示例,但也足以为它来作为开发应用的模板。
Demo的功能:

  • 获取与保存"单词";
  • 单词显示在MainActivity的RecyclerView中;
  • 通过悬浮按钮调起另一个activity,用于输入单词。
Demo的功能

RoomWordSample架构预览

下图展示了应用的各个部分。除了SQLite database,其他部分都用在自己创建的类中封装。
[图片上传失败...(image-30c198-1540955195680)]

你会学到什么

学会如何使用架构组件库和生命周期库设计和构建应用程序。
这里有许多步骤去使用架构组件和推荐的框架。最重要的是学会模型创建的作用、理解各部分组合与数据流向。通过这个Demo,你不单只是简单的复制和粘贴本文的代码,还要理解其内部原理。

你需要掌握什么

  • Android Studio 3.0或更高版本的使用。
  • 一台Android设备或模拟器。

你必须熟悉Java编程,面向对象设计,Android开发基础。尤其:

  • RecyclerView 及其适配器adapters
  • SQLite数据库及SQLite查询语言
  • 线程与AsyncTask
  • 了解一些数据与UI分离的构架概念,如MVP、MVC

本Demo着重于Android架构组件,非主要代码可行自行复制与粘贴。

2.创建应用

打开Android Studio创建应用:

  • 新建应用RoomWordSample,目标sdk为26+
  • 不选include Kotlin support和include C++ support
  • 下一步,选Phone & Tablet,minimum SDK选API 26
  • 下一步,选择Basic Activity
  • 下一步,完成。
    [图片上传失败...(image-cc06f7-1540954630792)]

3.更新gradle文件

添加组件库到gradle。在Module:app的build.gradle中dependencies末尾加入:

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

// Lifecycle components
implementation "android.arch.lifecycle:extensions:$rootProject.archLifecycleVersion"
annotationProcessor "android.arch.lifecycle:compiler:$rootProject.archLifecycleVersion"

中Project:RoomWordSample的build.gradle中加入版本信息:

ext {
   roomVersion = '1.1.1'
   archLifecycleVersion = '1.1.1'
}

4.创建实体

本demo的数据是“单词”,因此首先创建一个Word类,并为其创建构造函数与必要的get方法。这样Room才可以实例化对象。
[图片上传失败...(image-d84c98-1540954630792)]

下面是Word类:

public class Word {

   private String mWord;

   public Word(@NonNull String word) {this.mWord = word;}

   public String getWord(){return this.mWord;}
}

为了使Word类对Room库有意义,我们需要为它加注解。注解用了将实体与数据库相关联,Room根据相应的注解信息去生成对应的代码。

  • @Entity(tableName = "word_table") 每一个@Entity类代表数据库中的一张表。tableName为生成表的表名。
  • @PrimaryKey 每个实体需要一个主键。
  • @NonNull 表示参数、字段或返回值不能为null。
  • @ColumnInfo(name = "word") 指定与成员变量对应的列名。
  • 为一个字段需要是public的或提供get方法。

添加注解的Word类:

@Entity(tableName = "word_table")
public class Word {

   @PrimaryKey
   @NonNull
   @ColumnInfo(name = "word")
   private String mWord;

   public Word(String word) {this.mWord = word;}

   public String getWord(){return this.mWord;}
}

5.创建DAO(数据访问对象)

什么是DAO

DAO即数据访问对象,你可以指定SQL查询语句,并将它与方法关联起来。编译器会对常规SQL注解进行编译检查,如@Insert
DAO对象必须是个接口或抽象类。
默认情况下,所有的查询必须在单独线程中执行。
Room将通过DAO对象去创建相应的接口。

DAO的写法

DAO是代码的基础,它用于提供word的增、删、改、查。

  1. 创建一个名为WordDao的接口。
  2. 为WordDao添加@Dao注解
  3. 声明一个插入方法void insert(Word word);
  4. 为上述方法添加@Insert注解,并且不需要为其提供SQL语句!(同样的用法还有@Delete and @Update
  5. 声明方法void deleteAll();
  6. 这里没有方便的注解可以用于删除多个实体,因此需要用@Query注解
  7. 还需要为@Query注解提供SQL语句@Query("DELETE FROM word_table")
  8. 创建方法List<Word> getAllWords();
  9. 为其添加注解与SQL@Query("SELECT * from word_table ORDER BY word ASC")

下面是其完整的代码:

@Dao
public interface WordDao {

   @Insert
   void insert(Word word);

   @Query("DELETE FROM word_table")
   void deleteAll();

   @Query("SELECT * from word_table ORDER BY word ASC")
   List<Word> getAllWords();
}

6.LiveData类

当数据被改变后,通常你需要作一些操作,例如将更新的数据展示在UI上。这就意味着你必须去观察这些数据,以便于当数据改变时你能做出反应。根据数据不同的存储方式,这可能会很棘手。观察贯串多个组件中数据的变化,你必须要编写一个显式、严格依赖的调用链。这使得测试和调试变得非常困难。
LiveDatalifecycle library 中,用于数据观察的类,可用于解决上述难题。在你的方法中使用LiveData为返回值。这样Room将会为你生成所有必须的代码,当数据库更新时,自动去更新LiveData

使用LiveData的目的是为了管理数据的更新。但是LiveData类并没有提供公有的更新数据的方法。我们应当使用MutableLiveData,它有两个公有方法(setValue(T)postValue(T))用于存储数据。通常,MutableLiveData是在ViewModel中使用,然后ViewModel只向观察者暴露不可变的LiveData对象。

WordDao中,改变getAllWords()方法的返回值:

@Query("SELECT * from word_table ORDER BY word ASC")
LiveData<List<Word>> getAllWords();

后面我们会在MainActivityonCreate()方法中创建一个Observer对象,并覆盖其onChanged()方法。当LiveData改变时,观察者会被通知然后onChanged()会被回调。这时你可以更新适配器中的缓存数据,然后在适配器中更新UI。

7.添加Room数据库

什么是Room数据库?

Room是在SQLite之上的数据库层。Room用于处理我们曾经用SQLiteOpenHelper来处理任务。

  • Room通过DAO向数据库发送查询
  • 默认情况下,为了避免降低UI线程的性能,Room不允许在主线程中执行数据库操作
  • Room提供了编译时的SQL语句检查
  • 创建的Room类必须是抽象的,并且继承RoomDatabase
  • 通常,在整体应用中只需要一个Room数据库实例,即单例。

实现Room数据库

  1. 创建一个public abstractWordRoomDatabase,并继承RoomDatabase。即public abstract class WordRoomDatabase extends RoomDatabase {}
  2. 标注其为一个Room数据库,@Database(entities = {Word.class}, version = 1),声明其在数据库中的实体,并指定版本号。实体可以声明多个,声明的实体将在数据库中创建对应的表。
  3. 定义使用数据库的DAO。给每一个@Dao提供get方法。public abstract WordDao wordDao();

完整代码如下:

@Database(entities = {Word.class}, version = 1)
public abstract class WordRoomDatabase extends RoomDatabase {
   public abstract WordDao wordDao();

}
  1. 使WordRoomDatabase作为单例。
private static volatile WordRoomDatabase INSTANCE;

static WordRoomDatabase getDatabase(final Context context) {
    if (INSTANCE == null) {
        synchronized (WordRoomDatabase.class) {
           if (INSTANCE == null) {
                    // Create database here
           }
        }
    }
    return INSTANCE;
}
  1. 实例化RoomDatabase对象:使用Room的databaseBuilder,从WordRoomDatabase类的应用上下文context中创建RoomDatabase对象,并将数据库全名为"word_database"
// Create database here
INSTANCE = Room.databaseBuilder(context.getApplicationContext(),
       WordRoomDatabase.class, "word_database")
       .build();

下面是完整代码:

@Database(entities = {Word.class}, version = 1)
public abstract class WordRoomDatabase extends RoomDatabase {

   public abstract WordDao wordDao();

   private static volatile WordRoomDatabase INSTANCE;

   static WordRoomDatabase getDatabase(final Context context) {
        if (INSTANCE == null) {
            synchronized (WordRoomDatabase.class) {
                if (INSTANCE == null) {
                    INSTANCE = Room.databaseBuilder(context.getApplicationContext(),
                            WordRoomDatabase.class, "word_database")
                            .build();
                }
            }
        }
        return INSTANCE;
    }
}

当你修改数据库的schema时,你需要去更新版本号并声明如何进行数据迁移,例如销毁并重建数据库策略。具体的数据库迁移策略可参考Understanding migrations with Room

8.创建Repository(数据仓库)

什么是Repository?

Repository是一个可访问多数据源的类。它并非构架组件库中的一部分,但它是代码分离和体系结构的最佳实践建议。Repository用于处理数据操作,它为应用提供数据访问接口。
[图片上传失败...(image-aa37a9-1540954630792)]

为什么要使用Repository?

Repository管理查询线程,并允许您使用多个后端。在最常见的示例中,Repository实现了决定是从网络获取数据还是从本地缓存中获取结果的逻辑。

Repository的实现

  1. 创建一个公共类WordRepository
  2. 添加两个成员变量
private WordDao mWordDao;
private LiveData<List<Word>> mAllWords;
  1. 添加一个构造函数,该构造函数获取数据库的句柄并初始化成员变量。
WordRepository(Application application) {
    WordRoomDatabase db = WordRoomDatabase.getDatabase(application);
    mWordDao = db.wordDao();
    mAllWords = mWordDao.getAllWords();
}
  1. getAllWords()添加一个包装器。Room在单独的线程上执行所有查询。观察到LiveData数据更改时,将通知观察者。
LiveData<List<Word>> getAllWords() {
   return mAllWords;
}
  1. insert()方法添加一个包装器。使用AsyncTask来执行,确保其是在非UI线程中执行。
public void insert (Word word) {
    new InsertAsyncTask(mWordDao).execute(word);
}

6.InsertAsyncTask的实现

private static class insertAsyncTask extends AsyncTask<Word, Void, Void> {

    private WordDao mAsyncTaskDao;

    insertAsyncTask(WordDao dao) {
        mAsyncTaskDao = dao;
    }

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

下面是完整代码:

public class WordRepository {

   private WordDao mWordDao;
   private LiveData<List<Word>> mAllWords;

   WordRepository(Application application) {
       WordRoomDatabase db = WordRoomDatabase.getDatabase(application);
       mWordDao = db.wordDao();
       mAllWords = mWordDao.getAllWords();
   }

   LiveData<List<Word>> getAllWords() {
       return mAllWords;
   }


   public void insert (Word word) {
       new insertAsyncTask(mWordDao).execute(word);
   }

   private static class insertAsyncTask extends AsyncTask<Word, Void, Void> {

       private WordDao mAsyncTaskDao;

       insertAsyncTask(WordDao dao) {
           mAsyncTaskDao = dao;
       }

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

9.创建ViewModel

什么是ViewModel?

ViewModel的作用是向UI提供数据,并保存配置更改。ViewModel充当Repository 和UI之间的通信中心。还可以使用ViewModel在fragments之间共享数据。ViewModel是 lifecycle library 库的一部分。
[图片上传失败...(image-359dd8-1540954630792)]

为什么使用ViewModel?

当配置被更改时,ViewModel以一种有生命周期感知的方式保存应用的UI数据。将应用程序的UI数据与Activity和Fragment类分离,可以让你更好地遵循单一责任原则:你的activities和fragments负责将数据绘制到屏幕上,而ViewModel则负责保存和处理UI所需的所有数据。

ViewModel中,对于UI将使用或显示的可变数据,请使用LiveData。使用LiveData有几个好处:

  • 您可以在数据上放置一个观察者(而不是轮询更改),并且只在数据实际更改时更新UI。
  • Repository和UI由ViewModel完全分离。没有来自ViewModel的数据库调用,使得代码更易于测试。

ViewModel的实现

  1. 创建WordViewModel类,使其继承AndroidViewModel
public class WordViewModel extends AndroidViewModel {}
  1. 添加一个私有成员变量来保存对存储库的引用。
   private WordRepository mRepository;
  1. 添加一个私有LiveData成员变量来缓存单词列表。
  private LiveData<List<Word>> mAllWords;
  1. 添加一个构造函数,该构造函数获取对存储库的引用,并从存储库获取单词列表。
   public WordViewModel (Application application) {
       super(application);
       mRepository = new WordRepository(application);
       mAllWords = mRepository.getAllWords();
   }
  1. 为所有单词添加一个get方法。这完全隐藏了对UI的实现。
   LiveData<List<Word>> getAllWords() { return mAllWords; }
  1. 创建一个调用Repository的insert()方法的包装器insert()方法。这样,insert()的实现对于UI就完全透明了。
public void insert(Word word) { mRepository.insert(word); }

下面是WordViewModel的实现:

public class WordViewModel extends AndroidViewModel {

   private WordRepository mRepository;

   private LiveData<List<Word>> mAllWords;

   public WordViewModel (Application application) {
       super(application);
       mRepository = new WordRepository(application);
       mAllWords = mRepository.getAllWords();
   }

   LiveData<List<Word>> getAllWords() { return mAllWords; }

   public void insert(Word word) { mRepository.insert(word); }
}

警告:不要将context传递到ViewModel实例中。不要在ViewModel中存储活动、片段或视图实例或它们的context。

10.添加XML布局

value/styes.xml中添加列表项的样式:

<!-- The default font for RecyclerView items is too small.
The margin is a simple delimiter between the words. -->
<style name="word_title">
   <item name="android:layout_width">match_parent</item>
   <item name="android:layout_height">26dp</item>
   <item name="android:textSize">24sp</item>
   <item name="android:textStyle">bold</item>
   <item name="android:layout_marginBottom">6dp</item>
   <item name="android:paddingLeft">8dp</item>
</style>

添加一个layout/recyclerview_item.xml布局:

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

    <TextView
        android:id="@+id/textView"
        style="@style/word_title"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:background="@android:color/holo_orange_light" />
</LinearLayout>

Layout/Content_main.xml中,将TextView替换为ReccyclerView

<android.support.v7.widget.RecyclerView
   android:id="@+id/recyclerview"
   android:layout_width="match_parent"
   android:layout_height="match_parent"
   android:background="@android:color/darker_gray"
   tools:listitem="@layout/recyclerview_item" />

浮动动作按钮(FAB)应与可用动作相对应。在Layout/Activitymain.xml文件中,给FloatingActionButton一个+符号图标:

  1. Layout/Activitymain.xml文件中,选择File>New>VectorAsset。
  2. 选择Material Icon。
  3. 点击Android机器人图标:点field, 然后选 + ("add") 资源。
  4. 按以下方式更改布局文件代码。
android:src="@drawable/ic_add_black_24dp"

11.添加RecycleView

您将在RecycleView中显示数据,这比将数据抛到TextView中要好一些。
注意,适配器中的mWord变量缓存数据。在下一个任务中,添加自动更新数据的代码。
还请注意,getItemCount()方法需要优雅地考虑数据尚未准备好且mWord仍然为空的可能性。
添加一个类WordListAdapter,它扩展了ReccyclerView.Adapter
这是代码:

public class WordListAdapter extends RecyclerView.Adapter<WordListAdapter.WordViewHolder> {

   class WordViewHolder extends RecyclerView.ViewHolder {
       private final TextView wordItemView;

       private WordViewHolder(View itemView) {
           super(itemView);
           wordItemView = itemView.findViewById(R.id.textView);
       }
   }

   private final LayoutInflater mInflater;
   private List<Word> mWords; // Cached copy of words

   WordListAdapter(Context context) { mInflater = LayoutInflater.from(context); }

   @Override
   public WordViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
       View itemView = mInflater.inflate(R.layout.recyclerview_item, parent, false);
       return new WordViewHolder(itemView);
   }

   @Override
   public void onBindViewHolder(WordViewHolder holder, int position) {
       if (mWords != null) {
           Word current = mWords.get(position);
           holder.wordItemView.setText(current.getWord());
       } else {
           // Covers the case of data not being ready yet.
           holder.wordItemView.setText("No Word");
       }
   }

   void setWords(List<Word> words){
       mWords = words;
       notifyDataSetChanged();
   }

   // getItemCount() is called many times, and when it is first called,
   // mWords has not been updated (means initially, it's null, and we can't return null).
   @Override
   public int getItemCount() {
       if (mWords != null)
           return mWords.size();
       else return 0;
   }
}

MainActivityonCreate()方法中添加ReccyclerView
在onCreate()方法中:

RecyclerView recyclerView = findViewById(R.id.recyclerview);
final WordListAdapter adapter = new WordListAdapter(this);
recyclerView.setAdapter(adapter);
recyclerView.setLayoutManager(new LinearLayoutManager(this));

运行你的应用程序,以确保一切正常。没有项目,因为您还没有连接到数据,所以应用程序应该显示灰色背景,没有任何列表项目。
<img src="https://codelabs.developers.google.com/codelabs/android-room-with-a-view/img/193d01725acbe6cc.png" width="30%" height="30%" />

12.填充数据库

数据库中没有数据。您将以两种方式添加数据:打开数据库时添加一些数据,以及添加用于添加单词的Activity
要删除所有内容并在应用程序启动时重新填充数据库,您可以创建一个RoomDatabase.Callback并覆盖onOpen()。由于不能对UI线程执行Room数据库操作,因此onOpen()创建并执行AsyncTask来向数据库添加内容。

下面是在WordRoomDatabase类中创建回调的代码:

private static RoomDatabase.Callback sRoomDatabaseCallback = 
    new RoomDatabase.Callback(){

    @Override
    public void onOpen (@NonNull SupportSQLiteDatabase db){
        super.onOpen(db);
       new PopulateDbAsync(INSTANCE).execute();
   }
};

下面是AsyncTask的代码,它删除数据库的内容,然后用两个单词“Hello”和“World”填充数据库。欢迎加入更多的单词!

private static class PopulateDbAsync extends AsyncTask<Void, Void, Void> {

   private final WordDao mDao;

   PopulateDbAsync(WordRoomDatabase db) {
       mDao = db.wordDao();
   }

   @Override
   protected Void doInBackground(final Void... params) {
       mDao.deleteAll();
       Word word = new Word("Hello");
       mDao.insert(word);
       word = new Word("World");
       mDao.insert(word);
       return null;
   }
}

最后,在调用.build()之前,将回调添加到数据库构建序列。

.addCallback(sRoomDatabaseCallback)

13.添加NewWordActivity

将这些字符串资源添加到values/strings.xml中:

<string name="hint_word">Word...</string>
<string name="button_save">Save</string>
<string name="empty_not_saved">Word not saved because it is empty.</string>

value/color s.xml中添加此颜色资源:

<color name="buttonLabel">#d3d3d3</color>

将这些维度资源添加到values/dimens.xml

<dimen name="small_padding">6dp</dimen>
<dimen name="big_padding">16dp</dimen>

在布局文件夹中创建Activity_new_word.xml文件:

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

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

   <EditText
       android:id="@+id/edit_word"
       android:layout_width="match_parent"
       android:layout_height="wrap_content"
       android:fontFamily="sans-serif-light"
       android:hint="@string/hint_word"
       android:inputType="textAutoComplete"
       android:padding="@dimen/small_padding"
       android:layout_marginBottom="@dimen/big_padding"
       android:layout_marginTop="@dimen/big_padding"
       android:textSize="18sp" />

   <Button
       android:id="@+id/button_save"
       android:layout_width="match_parent"
       android:layout_height="wrap_content"
       android:background="@color/colorPrimary"
       android:text="@string/button_save"
       android:textColor="@color/buttonLabel" />

</LinearLayout>

使用空活动模板创建一个新活动,NewWordActivity。验证活动是否已添加到AndroidManifest中!

<activity android:name=".NewWordActivity"></activity>

下面是该activity的代码:

public class NewWordActivity extends AppCompatActivity {

   public static final String EXTRA_REPLY = "com.example.android.wordlistsql.REPLY";

   private  EditText mEditWordView;

   @Override
   public void onCreate(Bundle savedInstanceState) {
       super.onCreate(savedInstanceState);
       setContentView(R.layout.activity_new_word);
       mEditWordView = findViewById(R.id.edit_word);

       final Button button = findViewById(R.id.button_save);
       button.setOnClickListener(new View.OnClickListener() {
           public void onClick(View view) {
               Intent replyIntent = new Intent();
               if (TextUtils.isEmpty(mEditWordView.getText())) {
                   setResult(RESULT_CANCELED, replyIntent);
               } else {
                   String word = mEditWordView.getText().toString();
                   replyIntent.putExtra(EXTRA_REPLY, word);
                   setResult(RESULT_OK, replyIntent);
               }
               finish();
           }
       });
   }
}

14.连接数据

最后一步是通过保存用户输入的新单词并在RecyclerView中显示Word数据库的当前内容,将UI连接到数据库。

要显示数据库的当前内容,添加一个观察者来观察ViewModel中的LiveData。每当数据更改时,都会调用onchange()回调,该回调调用适配器的setWord()方法,以更新适配器的缓存数据并刷新显示的列表。

MainActivity中,为ViewModel创建一个成员变量:

private WordViewModel mWordViewModel;

使用ViewModelProvidersViewModel与UI控制器关联起来。当应用程序第一次启动时,ViewModelProviders将创建ViewModel。当activity 被销毁时,例如通过配置更改,ViewModel就会持续存在。重新创建activity 时,ViewModelProviders将返回现有的ViewModel。参见ViewModel

onCreate()中,从ViewModelProvider获取一个ViewModel

mWordViewModel = ViewModelProviders.of(this).get(WordViewModel.class);

同样在onCreate()中,为getAllWords()返回的LiveData添加一个观察者。当观察到的数据发生变化且activity 位于前台时,onchange()方法就会调用。

mWordViewModel.getAllWords().observe(this, new Observer<List<Word>>() {
   @Override
   public void onChanged(@Nullable final List<Word> words) {
       // Update the cached copy of the words in the adapter.
       adapter.setWords(words);
   }
});

MainActivity中,为NewWordActivity添加onActivityResult()代码。
如果activity返回RESULT_OK,则通过调用WordViewModelinsert()方法将返回的单词插入数据库。

public void onActivityResult(int requestCode, int resultCode, Intent data) {
   super.onActivityResult(requestCode, resultCode, data);

   if (requestCode == NEW_WORD_ACTIVITY_REQUEST_CODE && resultCode == RESULT_OK) {
       Word word = new Word(data.getStringExtra(NewWordActivity.EXTRA_REPLY));
       mWordViewModel.insert(word);
   } else {
       Toast.makeText(
               getApplicationContext(),
               R.string.empty_not_saved,
               Toast.LENGTH_LONG).show();
   }
}

定义缺少的请求代码:

public static final int NEW_WORD_ACTIVITY_REQUEST_CODE = 1;

MainActivity中,当用户点击Fab时启动NewWordActivity。用以下代码替换Fab的onclick()单击处理程序中的代码:

Intent intent = new Intent(MainActivity.this, NewWordActivity.class);
startActivityForResult(intent, NEW_WORD_ACTIVITY_REQUEST_CODE);

运行你的APP!!!
当您在NewWordActivity中向数据库添加一个单词时,UI将自动更新。

15.总结

[图片上传失败...(image-49e45a-1540955195680)]

现在你有了一个实用的应用程序,让我们回顾一下你已经构建了什么。这是最开发的应用程序的结构。
您有一个在列表中显示单词的应用程序(MainActivityReccyclerViewWordListAdapter)。
您可以向列表中添加单词(NewWordActivity)。
单词是单词实体类的实例。
这些单词作为单词(mWords) List缓存在RecyclerViewAdapter中。当数据库中的单词更改时,此单词列表会自动更新和重新显示。

用于自动UI更新的数据流(反应性UI)

自动更新是可能的,因为我们正在使用LiveData。在MainActivity中,有一个观察者从数据库中观察到LiveData这个词,并在它们更改时得到通知。当发生更改时,将执行观察者的onChange()方法,并更新WordListAdapter中的mWord

可以观察到数据,因为它是LiveData。观察到的是由WordViewModel对象的getAllWords()方法返回的LiveData<list<word>>

WordViewModel从UI层隐藏关于后端的所有内容。它提供访问数据层的方法,并返回LiveData,以便MainActivity可以设置观察者关系。Activities(和Fragments)仅通过ViewModel与数据交互。因此,数据从何而来并不重要。

在这个demo,数据来自一个存储库。ViewModel不需要知道这个仓库与什么交互。它只需要知道如何通过Repository公开的方法与Repository交互。

Repository 管理一个或多个数据源。在WordListSample应用程序中,后端是一个Room数据库。Room是一个包装器,实现了SQLite数据库。房间为你做了很多工作,你以前不得不自己做。例如,Room完成了以前使用SQLiteOpenHelper类所做的一切。

DAO映射方法调用数据库查询,以便当Repository调用getAllWords()等方法时,Room可以通过执行SELECT * from word_table ORDER BY word ASC

因为从查询返回的结果被观察到LiveData,所以每当Room中的数据发生变化时,会执行观察者接口的onChanged()方法,并更新UI。

15.代码

单击以下链接下载此codelab的解决方案代码:
RoomWordSample源码

15.进一步探索

如果您需要迁移应用程序,请参见成功完成此代码后的7 Steps To Room。请注意,删除SQLiteOpenHelper类和大量其他代码是非常令人满意的。

当您有大量数据时,请考虑使用paging library

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,716评论 4 364
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,558评论 1 294
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 109,431评论 0 244
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,127评论 0 209
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,511评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,692评论 1 222
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,915评论 2 313
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,664评论 0 202
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,412评论 1 246
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,616评论 2 245
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,105评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,424评论 2 254
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,098评论 3 238
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,096评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,869评论 0 197
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,748评论 2 276
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,641评论 2 271