Android LiveData

时间:2020-02-23 14:29:02  来源:igfitidea点击:

在本教程中,我们将讨论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中进行更新。