Android LiveData
在本教程中,我们将讨论Android应用程序中的LiveData体系结构组件。
为了更好地理解本教程,请快速绕开Android ViewModel。
Android LiveData
LiveData是体系结构模式的一部分。
它基本上是一个包含原始类型/集合类型的数据保存器。
用于观察视图中的更改并在活动状态下更新视图。
因此,LiveData是生命周期感知的。
我们知道ViewModel用于将数据传递给View。
单独使用ViewModels可能是一项繁琐且昂贵的操作,因为每次数据必须更改View时,我们都需要进行多次调用。
另外,我们需要将数据模型存储在不同的位置。
LiveData基于观察者模式,使ViewModel和View之间的通信变得容易。
它观察数据更改并自动更新数据,而不是我们从多个位置(例如SQLite,ArrayList,ViewModel)添加和删除数据引用中进行多次调用。
Android LiveData与RxJava
Android LiveData在某种程度上类似于RxJava,只是LiveData具有生命周期感知能力。
如果视图位于后台,则不会更新视图中的数据。
这有助于我们避免出现诸如" IllegalStateException"等异常。
ViewModel中的LiveData如何更新活动?当我们在Activity中注册观察者时,我们需要重写方法onChanged()。
每当更改LiveData时,方法onChanged()都会被触发。
因此,在onChanged()中,我们可以将更改后的LiveData更新到视图上。
LiveData只是一种数据类型,只要数据发生更改,它就会通知其观察者。
LiveData就像一个数据更改通知器。
LiveData使用setValue()和postValue()通知观察者。
setValue()在主线程上运行。
postValue()
在后台线程上运行。
在LiveData类型实例上调用getValue()
将返回您当前的数据。
MutableLiveData
MutableLiveData只是扩展LiveData类型类的类。
由于MutableLiveData公开提供了postValue()
,setValue()
方法,因此通常被使用,而LiveData类则不提供这种方法。
LiveData/MutableLiveData通常用于从集合类型(列表,数组列表等)更新RecyclerView中的数据。
在下一节中,我们将创建一个应用程序,该应用程序从SQLite数据库中添加/删除RecyclerView中的行。
每当LiveData发生更改时,我们将使用MutableLiveData
更新RecyclerView记录。
通过比较新旧ArrayList,我们将使用DiffUtil更新RecyclerView行的最小数量。
Android LiveData示例项目结构
在build.gradle文件中添加以下内容:
implementation 'com.android.support:design:27.1.1' implementation 'com.android.support:cardview-v7:27.1.1' implementation 'android.arch.lifecycle:extensions:1.1.1'
Android LiveData代码
下面给出了activity_main.xml布局的代码:
<?xml version="1.0" encoding="utf-8"?> <android.support.design.widget.CoordinatorLayout xmlns:android="https://schemas.android.com/apk/res/android" xmlns:app="https://schemas.android.com/apk/res-auto" xmlns:tools="https://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" tools:context=".MainActivity"> <android.support.design.widget.AppBarLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:theme="@style/AppTheme.AppBarOverlay"> <android.support.v7.widget.Toolbar android:id="@+id/toolbar" android:layout_width="match_parent" android:layout_height="?attr/actionBarSize" android:background="?attr/colorPrimary" app:popupTheme="@style/AppTheme.PopupOverlay" </android.support.design.widget.AppBarLayout> <RelativeLayout xmlns:android="https://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" app:layout_behavior="@string/appbar_scrolling_view_behavior"> <android.support.v7.widget.RecyclerView android:id="@+id/recyclerView" android:layout_width="match_parent" android:layout_height="match_parent" </RelativeLayout> <android.support.design.widget.FloatingActionButton android:id="@+id/fab" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="bottom|end" android:layout_margin="@dimen/fab_margin" app:srcCompat="@android:drawable/ic_input_add" </android.support.design.widget.CoordinatorLayout>
下面给出了list_item_row.xml布局的代码:
<?xml version="1.0" encoding="utf-8"?> <android.support.v7.widget.CardView xmlns:android="https://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="wrap_content"> <RelativeLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:padding="8dp" android:gravity="center_vertical"> <TextView android:id="@+id/tvUrl" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignParentStart="true" android:autoLink="web" android:padding="8dp" android:textColor="@android:color/black" android:textSize="20sp" <TextView android:id="@+id/tvDate" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignParentLeft="true" android:layout_below="@+id/tvUrl" <ImageButton android:id="@+id/btnDelete" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignParentEnd="true" android:layout_centerVertical="true" android:src="@android:drawable/ic_menu_delete" </RelativeLayout> </android.support.v7.widget.CardView>
下面给出了DbSettings.java
类的代码:
package com.theitroad.androidlivedata.db; import android.provider.BaseColumns; public class DbSettings { public static final String DB_NAME = "favourites.db"; public static final int DB_VERSION = 1; public class DBEntry implements BaseColumns { public static final String TABLE = "fav"; public static final String COL_FAV_URL = "url"; public static final String COL_FAV_DATE = "date"; } }
下面给出了FavouritesDbHelper.java类的代码:
package com.theitroad.androidlivedata.db; import android.content.Context; import android.database.sqlite.SQLiteDatabase; import android.database.sqlite.SQLiteOpenHelper; public class FavouritesDBHelper extends SQLiteOpenHelper { public FavouritesDBHelper(Context context) { super(context, DbSettings.DB_NAME, null, DbSettings.DB_VERSION); } @Override public void onCreate(SQLiteDatabase db) { String createTable = "CREATE TABLE " + DbSettings.DBEntry.TABLE + " ( " + DbSettings.DBEntry._ID + " INTEGER PRIMARY KEY AUTOINCREMENT, " + DbSettings.DBEntry.COL_FAV_URL + " TEXT NOT NULL, " + DbSettings.DBEntry.COL_FAV_DATE + " INTEGER NOT NULL);"; db.execSQL(createTable); } @Override public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { db.execSQL("DROP TABLE IF EXISTS " + DbSettings.DBEntry.TABLE); onCreate(db); } }
下面给出了Favourites.java模型类的代码:
package com.theitroad.androidlivedata; public class Favourites { public long mId; public String mUrl; public long mDate; public Favourites(long id, String name, long date) { mId = id; mUrl = name; mDate = date; } public Favourites(Favourites favourites) { mId = favourites.mId; mUrl = favourites.mUrl; mDate = favourites.mDate; } }
因此,在我们的SQLite数据库中,我们创建一个包含三个记录的表:ID,URL,DATE。
下面给出了FavouritesViewModel.java类的代码:
package com.theitroad.androidlivedata; import android.app.Application; import android.arch.lifecycle.AndroidViewModel; import android.arch.lifecycle.MutableLiveData; import android.content.ContentValues; import android.database.Cursor; import android.database.sqlite.SQLiteDatabase; import com.theitroad.androidlivedata.db.DbSettings; import com.theitroad.androidlivedata.db.FavouritesDBHelper; import java.util.ArrayList; import java.util.List; public class FavouritesViewModel extends AndroidViewModel { private FavouritesDBHelper mFavHelper; private MutableLiveData<List<Favourites>> mFavs; FavouritesViewModel(Application application) { super(application); mFavHelper = new FavouritesDBHelper(application); } public MutableLiveData<List<Favourites>> getFavs() { if (mFavs == null) { mFavs = new MutableLiveData<>(); loadFavs(); } return mFavs; } private void loadFavs() { List<Favourites> newFavs = new ArrayList<>(); SQLiteDatabase db = mFavHelper.getReadableDatabase(); Cursor cursor = db.query(DbSettings.DBEntry.TABLE, new String[]{ DbSettings.DBEntry._ID, DbSettings.DBEntry.COL_FAV_URL, DbSettings.DBEntry.COL_FAV_DATE }, null, null, null, null, null); while (cursor.moveToNext()) { int idxId = cursor.getColumnIndex(DbSettings.DBEntry._ID); int idxUrl = cursor.getColumnIndex(DbSettings.DBEntry.COL_FAV_URL); int idxDate = cursor.getColumnIndex(DbSettings.DBEntry.COL_FAV_DATE); newFavs.add(new Favourites(cursor.getLong(idxId), cursor.getString(idxUrl), cursor.getLong(idxDate))); } cursor.close(); db.close(); mFavs.setValue(newFavs); } public void addFav(String url, long date) { SQLiteDatabase db = mFavHelper.getWritableDatabase(); ContentValues values = new ContentValues(); values.put(DbSettings.DBEntry.COL_FAV_URL, url); values.put(DbSettings.DBEntry.COL_FAV_DATE, date); long id = db.insertWithOnConflict(DbSettings.DBEntry.TABLE, null, values, SQLiteDatabase.CONFLICT_REPLACE); db.close(); List<Favourites> favourites = mFavs.getValue(); ArrayList<Favourites> clonedFavs; if (favourites == null) { clonedFavs = new ArrayList<>(); } else { clonedFavs = new ArrayList<>(favourites.size()); for (int i = 0; i < favourites.size(); i++) { clonedFavs.add(new Favourites(favourites.get(i))); } } Favourites fav = new Favourites(id, url, date); clonedFavs.add(fav); mFavs.setValue(clonedFavs); } public void removeFav(long id) { SQLiteDatabase db = mFavHelper.getWritableDatabase(); db.delete( DbSettings.DBEntry.TABLE, DbSettings.DBEntry._ID + " = ?", new String[]{Long.toString(id)} ); db.close(); List<Favourites> favs = mFavs.getValue(); ArrayList<Favourites> clonedFavs = new ArrayList<>(favs.size()); for (int i = 0; i < favs.size(); i++) { clonedFavs.add(new Favourites(favs.get(i))); } int index = -1; for (int i = 0; i < clonedFavs.size(); i++) { Favourites favourites = clonedFavs.get(i); if (favourites.mId == id) { index = i; } } if (index != -1) { clonedFavs.remove(index); } mFavs.setValue(clonedFavs); } }
MutableLiveData拥有一个收藏夹实例对象列表。
在" addFav()"和" removeFav()"中,我们将数据更改通知给MainActivity中定义的观察者。
我们创建ArrayList的副本,以便比较新旧副本。
MainActivity.java类的代码如下:
package com.theitroad.androidlivedata; import android.arch.lifecycle.Observer; import android.arch.lifecycle.ViewModelProviders; import android.content.DialogInterface; import android.os.Bundle; import android.support.annotation.Nullable; import android.support.design.widget.FloatingActionButton; import android.support.v7.app.AlertDialog; import android.support.v7.app.AppCompatActivity; import android.support.v7.util.DiffUtil; import android.support.v7.widget.LinearLayoutManager; import android.support.v7.widget.RecyclerView; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.EditText; import android.widget.ImageButton; import android.widget.TextView; import java.util.Date; import java.util.List; public class MainActivity extends AppCompatActivity { private FavAdapter mFavAdapter; private FavouritesViewModel mFavViewModel; private List<Favourites> mFav; FloatingActionButton fab; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); fab = findViewById(R.id.fab); final RecyclerView recyclerView = findViewById(R.id.recyclerView); recyclerView.setLayoutManager(new LinearLayoutManager(this)); mFavViewModel = ViewModelProviders.of(this).get(FavouritesViewModel.class); final Observer<List<Favourites>> favsObserver = new Observer<List<Favourites>>() { @Override public void onChanged(@Nullable final List<Favourites> updatedList) { if (mFav == null) { mFav = updatedList; mFavAdapter = new FavAdapter(); recyclerView.setAdapter(mFavAdapter); } else { DiffUtil.DiffResult result = DiffUtil.calculateDiff(new DiffUtil.Callback() { @Override public int getOldListSize() { return mFav.size(); } @Override public int getNewListSize() { return updatedList.size(); } @Override public boolean areItemsTheSame(int oldItemPosition, int newItemPosition) { return mFav.get(oldItemPosition).mId == updatedList.get(newItemPosition).mId; } @Override public boolean areContentsTheSame(int oldItemPosition, int newItemPosition) { Favourites oldFav = mFav.get(oldItemPosition); Favourites newFav = updatedList.get(newItemPosition); return oldFav.equals(newFav); } }); result.dispatchUpdatesTo(mFavAdapter); mFav = updatedList; } } }; fab.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { final EditText inUrl = new EditText(MainActivity.this); AlertDialog dialog = new AlertDialog.Builder(MainActivity.this) .setTitle("New favourite") .setMessage("Add a url link below") .setView(inUrl) .setPositiveButton("Add", new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { String url = String.valueOf(inUrl.getText()); long date = (new Date()).getTime(); mFavViewModel.addFav(url, date); } }) .setNegativeButton("Cancel", null) .create(); dialog.show(); } }); mFavViewModel.getFavs().observe(this, favsObserver); } public class FavAdapter extends RecyclerView.Adapter<FavAdapter.FavViewHolder> { @Override public FavViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { View itemView = LayoutInflater.from(parent.getContext()).inflate(R.layout.list_item_row, parent, false); return new FavViewHolder(itemView); } @Override public void onBindViewHolder(FavViewHolder holder, int position) { Favourites favourites = mFav.get(position); holder.mTxtUrl.setText(favourites.mUrl); holder.mTxtDate.setText((new Date(favourites.mDate).toString())); } @Override public int getItemCount() { return mFav.size(); } class FavViewHolder extends RecyclerView.ViewHolder { TextView mTxtUrl; TextView mTxtDate; FavViewHolder(View itemView) { super(itemView); mTxtUrl = itemView.findViewById(R.id.tvUrl); mTxtDate = itemView.findViewById(R.id.tvDate); ImageButton btnDelete = itemView.findViewById(R.id.btnDelete); btnDelete.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { int pos = getAdapterPosition(); Favourites favourites = mFav.get(pos); mFavViewModel.removeFav(favourites.mId); } }); } } } }
在上面的代码中,我们在Activity本身中定义了ReyclerView Adapter类。
" mFavViewModel.getFavs()。
observe(this,favsObserver);"用于在MainActivity中设置一个Observer,每当LiveData更新时,ViewModel类都会通知该Observer。
" favsObserver"匿名类由" onChanged()"方法组成,该方法提供最新数据,然后在RecyclerView中进行更新。