Android ViewModel
在本教程中,我们将深入研究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中定义.