Android ViewModel

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

在本教程中,我们将深入研究ViewModel的概念。
我们将在Android中开发一个"收藏夹链接"应用程序,将其中的收藏夹链接保存到SQLite数据库中。
我们将看到ViewModel如何帮助我们构建强大的应用程序。

Android ViewModel

ViewModel是一个Android体系结构组件。
它在应用程序中用作数据管理器。

让Activity进行数据处理不是一个好主意。
ViewModel提供了一种创建和检索对象的方法。
它通常存储视图数据的状态并与其他组件通信。

配置更改和活动数据被清除时非常方便。
配置更改不会影响ViewModel,因为它并未完全绑定到Activity。
重新创建活动后,它可以再次将数据提供给活动。

简单规则:不要让您的android类处理所有操作。
特别是没有数据。

Android SQLite

在本教程中,我们已经深入讨论了SQLite。
在该教程中,我们在Activity中完成了所有SQLite处理,并使其成为一个繁重的类。

其中我们将在ViewModel类中执行SQLite查询。

回顾一下:为了将数据添加到SQLite数据库,我们使用ContentValue,就像数据存储一样。
我们以键值对的形式传递数据。

为了从SQLite检索数据,使用了一个Cursor对象。

在下一节中,我们将创建一个将ListView记录保存在SQLite表中的应用程序。

您可以将喜爱的Web链接添加/删除到ListView,该链接将从ViewModel在数据库中进行更新。

Android ViewModel教程项目结构

在您应用的" build.gradle"文件中添加以下库:

implementation 'com.android.support:design:27.1.1'
implementation "android.arch.lifecycle:extensions:1.1.1"

Android ViewModel代码

DB文件夹包含SQLite设置。
下面是DbSettings.java类的代码:

package com.theitroad.androidviewmodel.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.androidviewmodel.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);
  }

}

我们的数据库由三列组成-ID,URL,DATE。

下面给出了Favourites.java类的代码:

package com.theitroad.androidviewmodel;

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;
  }

}

下面给出了FavouritesViewModel.java类的代码:

package com.theitroad.androidviewmodel;

import android.app.Application;
import android.arch.lifecycle.AndroidViewModel;
import android.content.ContentValues;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.util.Log;

import com.theitroad.androidviewmodel.db.DbSettings;
import com.theitroad.androidviewmodel.db.FavouritesDBHelper;

import java.util.ArrayList;
import java.util.Date;
import java.util.List;

public class FavouritesViewModel extends AndroidViewModel {

  private FavouritesDBHelper mFavHelper;
  private ArrayList<Favourites> mFavs;

  FavouritesViewModel(Application application) {
      super(application);
      mFavHelper = new FavouritesDBHelper(application);
  }

  public List<Favourites> getFavs() {
      if (mFavs == null) {
          mFavs = new ArrayList<>();

          createDummyList();
          loadFavs();
      }
      ArrayList<Favourites> clonedFavs = new ArrayList<>(mFavs.size());
      for (int i = 0; i < mFavs.size(); i++) {
          clonedFavs.add(new Favourites(mFavs.get(i)));
      }
      return clonedFavs;
  }

  public void createDummyList() {

      addFav("https://www.theitroad.local", (new Date()).getTime());
      addFav("https://www.medium.com", (new Date()).getTime());
      addFav("https://www.reddit.com", (new Date()).getTime());
      addFav("https://www.github.com", (new Date()).getTime());
      addFav("https://www.hackerrank.com", (new Date()).getTime());
      addFav("https://www.developers.android.com", (new Date()).getTime());
  }

  private void loadFavs() {

      mFavs.clear();

      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);
          mFavs.add(new Favourites(cursor.getLong(idxId), cursor.getString(idxUrl), cursor.getLong(idxDate)));
      }
      cursor.close();
      db.close();
  }

  public Favourites addFav(String url, long date) {
      
      Log.d("API123", "addFav " + url);

      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();
      Favourites fav = new Favourites(id, url, date);
      mFavs.add(fav);
      return new Favourites(fav);
  }

  public void removeFav(long id) {
      SQLiteDatabase db = mFavHelper.getWritableDatabase();
      db.delete(
              DbSettings.DBEntry.TABLE,
              DbSettings.DBEntry._ID + " = ?",
              new String[]{Long.toString(id)}
      );
      db.close();
      
      int index = -1;
      for (int i = 0; i < mFavs.size(); i++) {
          Favourites favourites = mFavs.get(i);
          if (favourites.mId == id) {
              index = i;
          }
      }
      if (index != -1) {
          mFavs.remove(index);
      }
  }

}

在上面的代码中,我们从SQLite数据库中添加,删除和加载数据。

在getFavs()方法中,我们对ArrayList进行了深层克隆。

ViewModel是我们的View的数据管理器。
它按需提供数据。

从我们的Activity类中调用addFav()和removeFav()方法进行两项操作:

  • 更改SQLite数据库
  • 更改通过ArrayList提供给ListView的数据。

因此,每次视图需要某些内容时,我们的ViewModel必须执行两次调用。
我们将在以后的教程中看到如何使用LiveData对此进行优化。

下面给出了activity_main.xml布局的代码:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="https://schemas.android.com/apk/res/android"
  android:id="@+id/rlLayout"
  android:layout_width="match_parent"
  android:layout_height="match_parent">

  <ListView
      android:id="@+id/listView"
      android:layout_width="match_parent"
      android:layout_height="match_parent" 

  <android.support.design.widget.FloatingActionButton
      android:id="@+id/fab"
      android:layout_width="wrap_content"
      android:layout_height="wrap_content"
      android:layout_alignParentBottom="true"
      android:layout_alignParentRight="true"
      android:src="@android:drawable/ic_input_add"
      android:layout_margin="16dp" 

</RelativeLayout>

list_item_row.xml的代码如下:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="https://schemas.android.com/apk/res/android"
  android:layout_width="match_parent"
  android:layout_height="wrap_content"
  android:padding="10dp">

  <LinearLayout xmlns:android="https://schemas.android.com/apk/res/android"
      android:layout_width="wrap_content"
      android:layout_height="wrap_content"
      android:layout_centerVertical="true"
      android:orientation="vertical">

      <TextView
          android:id="@+id/tvUrl"
          android:layout_width="wrap_content"
          android:layout_height="wrap_content"
          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" 
  </LinearLayout>

  <ImageButton
      android:id="@+id/btnDelete"
      android:layout_width="wrap_content"
      android:layout_height="wrap_content"
      android:layout_alignParentEnd="true"
      android:layout_alignParentRight="true"
      android:layout_centerVertical="true"
      android:onClick="deleteFav"
      android:src="@android:drawable/ic_menu_delete" 
</RelativeLayout>

MainActivity.java类的代码如下:

package com.theitroad.androidviewmodel;

import android.arch.lifecycle.ViewModelProviders;
import android.content.Context;
import android.content.DialogInterface;
import android.support.annotation.NonNull;
import android.support.design.widget.FloatingActionButton;
import android.support.v7.app.AlertDialog;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import android.widget.EditText;
import android.widget.ListView;
import android.widget.TextView;

import java.util.Date;
import java.util.List;

public class MainActivity extends AppCompatActivity {

  private FavouritesAdapter mFavAdapter;
  private FavouritesViewModel mFavViewModel;

  @Override
  protected void onCreate(Bundle savedInstanceState) {
      super.onCreate(savedInstanceState);
      setContentView(R.layout.activity_main);

      ListView listView = findViewById(R.id.listView);
      FloatingActionButton fab = findViewById(R.id.fab);

      mFavViewModel = ViewModelProviders.of(this).get(FavouritesViewModel.class);

      List<Favourites> favourites = mFavViewModel.getFavs();
      mFavAdapter = new FavouritesAdapter(this, R.layout.list_item_row, favourites);
      listView.setAdapter(mFavAdapter);

      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();
                              //VM AND VIEW
                              mFavAdapter.add(mFavViewModel.addFav(url, date));
                          }
                      })
                      .setNegativeButton("Cancel", null)
                      .create();
              dialog.show();
          }
      });
  }

  public void deleteFav(View view) {
      View parent = (View) view.getParent();
      int position = (int) parent.getTag(R.id.POS);
      Favourites favourites = mFavAdapter.getItem(position);
      mFavViewModel.removeFav(favourites.mId);
      mFavAdapter.remove(favourites);
  }

  public class FavouritesAdapter extends ArrayAdapter<Favourites> {

      private class ViewHolder {
          TextView tvUrl;
          TextView tvDate;
      }

      public FavouritesAdapter(Context context, int layoutResourceId, List<Favourites> todos) {
          super(context, layoutResourceId, todos);
      }

      @Override
      @NonNull
      public View getView(int position, View convertView, ViewGroup parent) {
          Favourites favourites = getItem(position);
          ViewHolder viewHolder;
          if (convertView == null) {
              viewHolder = new ViewHolder();
              LayoutInflater vi = (LayoutInflater) getSystemService(Context.LAYOUT_INFLATER_SERVICE);
              convertView = vi.inflate(R.layout.list_item_row, parent, false);
              viewHolder.tvUrl = convertView.findViewById(R.id.tvUrl);
              viewHolder.tvDate = convertView.findViewById(R.id.tvDate);
              convertView.setTag(R.id.VH, viewHolder);
          } else {
              viewHolder = (ViewHolder) convertView.getTag(R.id.VH);

          }

          viewHolder.tvUrl.setText(favourites.mUrl);
          viewHolder.tvDate.setText((new Date(favourites.mDate).toString()));
          convertView.setTag(R.id.POS, position);
          return convertView;
      }

  }
}

在上面的代码中,我们为ListView使用了ArrayAdapter类。

在我们的FloatingActionButton中,我们创建一个带有EditText的AlertDialog,以在ListView中添加新的URL。

ViewModel将其添加到SQLite和ArrayList。

在XML中为每个列表行定义了一个ImageButton。
那里的android:onClick方法调用了deleteFav()方法。

deleteFav中,我们触发了两个调用-一个调用ViewModel从SQLite删除记录,另一个调用ArrayList。
这很贵。
我们将在以后的教程中看到LiveData如何对此进行优化。

点击tvUrl会将URL直接启动到浏览器中,因为我们设置了android:autoLink =" true"

你知道吗?扩展ArrayAdapter类的适配器必须通过构造函数传递布局资源。
否则可能导致ListView中的行重复。

我们已将ID标签分配给View Holder和ListView Adapter中的视图。

标签id在values文件夹中的文件tags.xml中定义.