美文网首页Android Jetpack
Android Jetpack架构篇:带视图的Android R

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

作者: walker不抽烟 | 来源:发表于2018-10-31 10:52 被阅读286次

    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

    相关文章

      网友评论

        本文标题:Android Jetpack架构篇:带视图的Android R

        本文链接:https://www.haomeiwen.com/subject/zmgotqtx.html