在本教程中,我们将讨论Android应用程序中的LiveData架构组件。要更好地理解本教程,请快速转到Android ViewModel.
Android LiveData
LiveData是架构模式的一部分。它基本上是一个包含基元/集合类型的数据保持器。它用于观察视图中的更改,并在视图处于活动状态时更新视图。因此,LiveData具有生命周期意识。我们知道,视图模型用于将数据传递给视图。单独使用ViewModel可能是一项乏味且代价高昂的操作,因为每次数据必须更改View时,我们都需要进行多次调用。此外,我们还需要将数据模型存储在不同的位置。LiveData基于观察者Pattern],使得视图模型和视图之间的通信变得容易。它观察数据更改并自动更新数据,而不是我们在从多个位置(例如SQLite,ArrayList,视图模型)添加和删除数据引用时进行多次调用。
Android LiveData vs RxJava
Android LiveData类似于RxJava,不同之处在于LiveData是生命周期感知的。如果视图在后台,它不会更新视图中的数据。这有助于我们避免IlLegalStateException
等异常。我们在ViewModel中的LiveData是如何更新Activity的? 当我们在Activity中注册观察者时,我们需要重写方法onChanged()
。只要LiveData发生更改,onChanged()方法就会触发。因此,在onChanged()
中,我们可以将更改后的LiveData更新到View上。
LiveData只是一种数据类型,当数据发生变化时,它会通知它的观察者。LiveData就像一个数据更改通知程序。
LiveData使用setValue()
和postValue()
通知观察者。setValue()
在主线程上运行。postValue()
在后台线程上运行。在LiveData类型实例上调用getValue()
将返回当前数据。
MuableLiveData
MuableLiveData只是一个扩展LiveData类型类的类。MuableLiveData之所以常用,是因为它公开提供了postValue()
,setValue()
方法,这是LiveData类所不提供的。LiveData/MuableLiveData通常用于从集合类型(列表、ArrayList等)更新列表中的数据。在下一节中,我们将创建一个应用程序,该应用程序从SQLite数据库中添加/删除RecillerView中的行。我们将使用MuableLiveData
,它会在LiveData发生变化时更新RecclerView记录。我们将使用DiffUtil通过比较旧的和新的ArrayList来更新最小数量的回收视图行。
Android LiveData项目结构示例
1implementation 'com.android.support:design:27.1.1'
2implementation 'com.android.support:cardview-v7:27.1.1'
3implementation 'android.arch.lifecycle:extensions:1.1.1'
Android LiveData代码
Active_main.xml布局的代码如下所示:
1<?xml version="1.0" encoding="utf-8"?>
2<android.support.design.widget.CoordinatorLayout xmlns:android="https://schemas.android.com/apk/res/android"
3 xmlns:app="https://schemas.android.com/apk/res-auto"
4 xmlns:tools="https://schemas.android.com/tools"
5 android:layout_width="match_parent"
6 android:layout_height="match_parent"
7 tools:context=".MainActivity">
8
9 <android.support.design.widget.AppBarLayout
10 android:layout_width="match_parent"
11 android:layout_height="wrap_content"
12 android:theme="@style/AppTheme.AppBarOverlay">
13
14 <android.support.v7.widget.Toolbar
15 android:id="@+id/toolbar"
16 android:layout_width="match_parent"
17 android:layout_height="?attr/actionBarSize"
18 android:background="?attr/colorPrimary"
19 app:popupTheme="@style/AppTheme.PopupOverlay" />
20
21 </android.support.design.widget.AppBarLayout>
22
23 <RelativeLayout xmlns:android="https://schemas.android.com/apk/res/android"
24 android:layout_width="match_parent"
25 android:layout_height="match_parent"
26 app:layout_behavior="@string/appbar_scrolling_view_behavior">
27
28 <android.support.v7.widget.RecyclerView
29 android:id="@+id/recyclerView"
30 android:layout_width="match_parent"
31 android:layout_height="match_parent" />
32
33 </RelativeLayout>
34
35 <android.support.design.widget.FloatingActionButton
36 android:id="@+id/fab"
37 android:layout_width="wrap_content"
38 android:layout_height="wrap_content"
39 android:layout_gravity="bottom|end"
40 android:layout_margin="@dimen/fab_margin"
41 app:srcCompat="@android:drawable/ic_input_add" />
42
43</android.support.design.widget.CoordinatorLayout>
List_Item_row.xml布局的代码如下:
1<?xml version="1.0" encoding="utf-8"?>
2<android.support.v7.widget.CardView xmlns:android="https://schemas.android.com/apk/res/android"
3 android:layout_width="match_parent"
4 android:layout_height="wrap_content">
5
6 <RelativeLayout
7 android:layout_width="match_parent"
8 android:layout_height="wrap_content"
9 android:padding="8dp"
10 android:gravity="center_vertical">
11
12 <TextView
13 android:id="@+id/tvUrl"
14 android:layout_width="wrap_content"
15 android:layout_height="wrap_content"
16 android:layout_alignParentStart="true"
17 android:autoLink="web"
18 android:padding="8dp"
19 android:textColor="@android:color/black"
20 android:textSize="20sp" />
21
22 <TextView
23 android:id="@+id/tvDate"
24 android:layout_width="wrap_content"
25 android:layout_height="wrap_content"
26 android:layout_alignParentLeft="true"
27 android:layout_below="@+id/tvUrl" />
28
29 <ImageButton
30 android:id="@+id/btnDelete"
31 android:layout_width="wrap_content"
32 android:layout_height="wrap_content"
33 android:layout_alignParentEnd="true"
34 android:layout_centerVertical="true"
35 android:src="@android:drawable/ic_menu_delete" />
36
37 </RelativeLayout>
38
39</android.support.v7.widget.CardView>
DbSettings.java
类的代码如下:
1package com.journaldev.androidlivedata.db;
2
3import android.provider.BaseColumns;
4
5public class DbSettings {
6
7 public static final String DB_NAME = "favourites.db";
8 public static final int DB_VERSION = 1;
9
10 public class DBEntry implements BaseColumns {
11
12 public static final String TABLE = "fav";
13 public static final String COL_FAV_URL = "url";
14 public static final String COL_FAV_DATE = "date";
15
16 }
17}
FavouritesDbHelper.java类的代码如下:
1package com.journaldev.androidlivedata.db;
2
3import android.content.Context;
4import android.database.sqlite.SQLiteDatabase;
5import android.database.sqlite.SQLiteOpenHelper;
6
7public class FavouritesDBHelper extends SQLiteOpenHelper {
8
9 public FavouritesDBHelper(Context context) {
10 super(context, DbSettings.DB_NAME, null, DbSettings.DB_VERSION);
11 }
12
13 @Override
14 public void onCreate(SQLiteDatabase db) {
15 String createTable = "CREATE TABLE " + DbSettings.DBEntry.TABLE + " ( " +
16 DbSettings.DBEntry._ID + " INTEGER PRIMARY KEY AUTOINCREMENT, " +
17 DbSettings.DBEntry.COL_FAV_URL + " TEXT NOT NULL, " +
18 DbSettings.DBEntry.COL_FAV_DATE + " INTEGER NOT NULL);";
19 db.execSQL(createTable);
20 }
21
22 @Override
23 public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
24 db.execSQL("DROP TABLE IF EXISTS " + DbSettings.DBEntry.TABLE);
25 onCreate(db);
26 }
27
28}
Favourites.Java模型类的代码如下:
1package com.journaldev.androidlivedata;
2
3public class Favourites {
4
5 public long mId;
6 public String mUrl;
7 public long mDate;
8
9 public Favourites(long id, String name, long date) {
10 mId = id;
11 mUrl = name;
12 mDate = date;
13 }
14
15 public Favourites(Favourites favourites) {
16 mId = favourites.mId;
17 mUrl = favourites.mUrl;
18 mDate = favourites.mDate;
19 }
20
21}
因此,在我们的SQLite数据库中,我们创建了一个包含三条记录的表:ID、URL、DATE。FavouritesViewModel.Java类的代码如下:
1package com.journaldev.androidlivedata;
2
3import android.app.Application;
4import android.arch.lifecycle.AndroidViewModel;
5import android.arch.lifecycle.MutableLiveData;
6import android.content.ContentValues;
7import android.database.Cursor;
8import android.database.sqlite.SQLiteDatabase;
9
10import com.journaldev.androidlivedata.db.DbSettings;
11import com.journaldev.androidlivedata.db.FavouritesDBHelper;
12import java.util.ArrayList;
13import java.util.List;
14
15public class FavouritesViewModel extends AndroidViewModel {
16
17 private FavouritesDBHelper mFavHelper;
18 private MutableLiveData<List<Favourites>> mFavs;
19
20 FavouritesViewModel(Application application) {
21 super(application);
22 mFavHelper = new FavouritesDBHelper(application);
23 }
24
25 public MutableLiveData<List<Favourites>> getFavs() {
26 if (mFavs == null) {
27 mFavs = new MutableLiveData<>();
28 loadFavs();
29 }
30
31 return mFavs;
32 }
33
34 private void loadFavs() {
35 List<Favourites> newFavs = new ArrayList<>();
36 SQLiteDatabase db = mFavHelper.getReadableDatabase();
37 Cursor cursor = db.query(DbSettings.DBEntry.TABLE,
38 new String[]{
39 DbSettings.DBEntry._ID,
40 DbSettings.DBEntry.COL_FAV_URL,
41 DbSettings.DBEntry.COL_FAV_DATE
42 },
43 null, null, null, null, null);
44 while (cursor.moveToNext()) {
45 int idxId = cursor.getColumnIndex(DbSettings.DBEntry._ID);
46 int idxUrl = cursor.getColumnIndex(DbSettings.DBEntry.COL_FAV_URL);
47 int idxDate = cursor.getColumnIndex(DbSettings.DBEntry.COL_FAV_DATE);
48 newFavs.add(new Favourites(cursor.getLong(idxId), cursor.getString(idxUrl), cursor.getLong(idxDate)));
49 }
50
51 cursor.close();
52 db.close();
53 mFavs.setValue(newFavs);
54 }
55
56 public void addFav(String url, long date) {
57
58 SQLiteDatabase db = mFavHelper.getWritableDatabase();
59 ContentValues values = new ContentValues();
60 values.put(DbSettings.DBEntry.COL_FAV_URL, url);
61 values.put(DbSettings.DBEntry.COL_FAV_DATE, date);
62 long id = db.insertWithOnConflict(DbSettings.DBEntry.TABLE,
63 null,
64 values,
65 SQLiteDatabase.CONFLICT_REPLACE);
66 db.close();
67
68 List<Favourites> favourites = mFavs.getValue();
69
70 ArrayList<Favourites> clonedFavs;
71 if (favourites == null) {
72 clonedFavs = new ArrayList<>();
73 } else {
74 clonedFavs = new ArrayList<>(favourites.size());
75 for (int i = 0; i < favourites.size(); i++) {
76 clonedFavs.add(new Favourites(favourites.get(i)));
77 }
78 }
79
80 Favourites fav = new Favourites(id, url, date);
81 clonedFavs.add(fav);
82 mFavs.setValue(clonedFavs);
83 }
84
85 public void removeFav(long id) {
86 SQLiteDatabase db = mFavHelper.getWritableDatabase();
87 db.delete(
88 DbSettings.DBEntry.TABLE,
89 DbSettings.DBEntry._ID + " = ?",
90 new String[]{Long.toString(id)}
91 );
92 db.close();
93
94 List<Favourites> favs = mFavs.getValue();
95 ArrayList<Favourites> clonedFavs = new ArrayList<>(favs.size());
96 for (int i = 0; i < favs.size(); i++) {
97 clonedFavs.add(new Favourites(favs.get(i)));
98 }
99
100 int index = -1;
101 for (int i = 0; i < clonedFavs.size(); i++) {
102 Favourites favourites = clonedFavs.get(i);
103 if (favourites.mId == id) {
104 index = i;
105 }
106 }
107 if (index != -1) {
108 clonedFavs.remove(index);
109 }
110 mFavs.setValue(clonedFavs);
111 }
112
113}
MuableLiveData保存最喜欢的实例对象列表。在addFav()
和emoveFav()
中,我们将数据更改通知给在MainActivity中定义的观察者。我们创建一个ArrayList的副本,以便比较新旧的。MainActivity.Java类的代码如下:
1package com.journaldev.androidlivedata;
2
3import android.arch.lifecycle.Observer;
4import android.arch.lifecycle.ViewModelProviders;
5import android.content.DialogInterface;
6import android.os.Bundle;
7import android.support.annotation.Nullable;
8import android.support.design.widget.FloatingActionButton;
9import android.support.v7.app.AlertDialog;
10import android.support.v7.app.AppCompatActivity;
11import android.support.v7.util.DiffUtil;
12import android.support.v7.widget.LinearLayoutManager;
13import android.support.v7.widget.RecyclerView;
14import android.view.LayoutInflater;
15import android.view.View;
16import android.view.ViewGroup;
17import android.widget.EditText;
18import android.widget.ImageButton;
19import android.widget.TextView;
20
21import java.util.Date;
22import java.util.List;
23
24public class MainActivity extends AppCompatActivity {
25
26 private FavAdapter mFavAdapter;
27 private FavouritesViewModel mFavViewModel;
28 private List<Favourites> mFav;
29 FloatingActionButton fab;
30
31 @Override
32 protected void onCreate(Bundle savedInstanceState) {
33 super.onCreate(savedInstanceState);
34 setContentView(R.layout.activity_main);
35 fab = findViewById(R.id.fab);
36 final RecyclerView recyclerView = findViewById(R.id.recyclerView);
37 recyclerView.setLayoutManager(new LinearLayoutManager(this));
38 mFavViewModel = ViewModelProviders.of(this).get(FavouritesViewModel.class);
39 final Observer<List<Favourites>> favsObserver = new Observer<List<Favourites>>() {
40 @Override
41 public void onChanged(@Nullable final List<Favourites> updatedList) {
42 if (mFav == null) {
43 mFav = updatedList;
44 mFavAdapter = new FavAdapter();
45 recyclerView.setAdapter(mFavAdapter);
46 } else {
47 DiffUtil.DiffResult result = DiffUtil.calculateDiff(new DiffUtil.Callback() {
48
49 @Override
50 public int getOldListSize() {
51 return mFav.size();
52 }
53
54 @Override
55 public int getNewListSize() {
56 return updatedList.size();
57 }
58
59 @Override
60 public boolean areItemsTheSame(int oldItemPosition, int newItemPosition) {
61 return mFav.get(oldItemPosition).mId ==
62 updatedList.get(newItemPosition).mId;
63 }
64
65 @Override
66 public boolean areContentsTheSame(int oldItemPosition, int newItemPosition) {
67 Favourites oldFav = mFav.get(oldItemPosition);
68 Favourites newFav = updatedList.get(newItemPosition);
69 return oldFav.equals(newFav);
70 }
71 });
72 result.dispatchUpdatesTo(mFavAdapter);
73 mFav = updatedList;
74 }
75 }
76 };
77
78 fab.setOnClickListener(new View.OnClickListener() {
79 @Override
80 public void onClick(View view) {
81 final EditText inUrl = new EditText(MainActivity.this);
82 AlertDialog dialog = new AlertDialog.Builder(MainActivity.this)
83 .setTitle("New favourite")
84 .setMessage("Add a url link below")
85 .setView(inUrl)
86 .setPositiveButton("Add", new DialogInterface.OnClickListener() {
87 @Override
88 public void onClick(DialogInterface dialog, int which) {
89 String url = String.valueOf(inUrl.getText());
90 long date = (new Date()).getTime();
91
92 mFavViewModel.addFav(url, date);
93 }
94 })
95 .setNegativeButton("Cancel", null)
96 .create();
97 dialog.show();
98 }
99 });
100
101 mFavViewModel.getFavs().observe(this, favsObserver);
102 }
103
104 public class FavAdapter extends RecyclerView.Adapter<FavAdapter.FavViewHolder> {
105
106 @Override
107 public FavViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
108 View itemView = LayoutInflater.from(parent.getContext()).inflate(R.layout.list_item_row, parent, false);
109 return new FavViewHolder(itemView);
110 }
111
112 @Override
113 public void onBindViewHolder(FavViewHolder holder, int position) {
114 Favourites favourites = mFav.get(position);
115 holder.mTxtUrl.setText(favourites.mUrl);
116 holder.mTxtDate.setText((new Date(favourites.mDate).toString()));
117 }
118
119 @Override
120 public int getItemCount() {
121 return mFav.size();
122 }
123
124 class FavViewHolder extends RecyclerView.ViewHolder {
125
126 TextView mTxtUrl;
127 TextView mTxtDate;
128
129 FavViewHolder(View itemView) {
130 super(itemView);
131 mTxtUrl = itemView.findViewById(R.id.tvUrl);
132 mTxtDate = itemView.findViewById(R.id.tvDate);
133 ImageButton btnDelete = itemView.findViewById(R.id.btnDelete);
134 btnDelete.setOnClickListener(new View.OnClickListener() {
135 @Override
136 public void onClick(View view) {
137 int pos = getAdapterPosition();
138 Favourites favourites = mFav.get(pos);
139 mFavViewModel.removeFav(favourites.mId);
140 }
141 });
142 }
143 }
144 }
145
146}
在上面的代码中,我们在活动本身中定义了ReclerView Adapter类。mFavViewModel.getFavs().Watch(this,avesWatch);
用于在MainActivity中设置一个观察者,每当更新LiveData时,该观察者都会从ViewModel类中得到通知。bensObserver
匿名类由onChanged()
方法组成,该方法提供最新的数据,然后在RecillerView中进行更新。运行中的上述应用程序的输出如下所示。这将结束Android LiveData教程。您可以从下面的链接下载该项目。
下载AndroidLiveData项目。
您也可以从我们的giHub Repository]签出Android Studio项目代码