Android ViewModel

In this tutorial, we’ll be digging deep into the concept of ViewModel. We’ll be developing a Favourite Links Application in Android wherein we can save our favorite links onto the SQLite database. We’ll see how ViewModel helps us in building a robust app.

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

Android ViewModel (Android ViewModel)

ViewModel is an Android architecture component. It is used as a data manager in the application.

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

Letting the Activity do the data handling isn’t a good idea. A ViewModel provides a way to create and retrieve objects. It typically stores the state of a view’s data and communicates with other components.

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

It is handy when it comes to configuration changes and the Activity’s data gets erased. The configuration change doesn’t impact the ViewModel since it is not tied to the Activity completely. It is able to provide the data to the Activity again after it gets recreated.

当涉及到配置更改并且活动的数据被删除时,它很方便。 配置更改不会影响ViewModel,因为它没有完全绑定到Activity。 重新创建活动后,它可以再次将数据提供给活动。

Simple Rule: Don’t let your android classes handle everything. Especially not data.
简单规则:不要让您的android类处理所有事情。 特别是没有数据。

Android SQLite (Android SQLite)

We’ve discussed SQLite at depth in this tutorial. In that tutorial, we did all the SQLite handling in our Activity and made it a heavy class.

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

Here we’ll do the SQLite querying in our ViewModel class.

在这里,我们将在ViewModel类中执行SQLite查询。

To recap: For adding data to SQLite database, we use a ContentValue which is like a data storage. We pass the data in the form of a key-value pair.

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

To retrieve the data from the SQLite a Cursor object is used.

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

In the following section, we’ll create an application that holds ListView records in an SQLite table.

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

You can add/delete your favorite web links to the ListView which would get updated in the database from the ViewModel.

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

Android ViewModel教程项目结构 (Android ViewModel Tutorial Project Structure)

Add the following libraries in your app’s build.gradle file:

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

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

Android ViewModel代码 (Android ViewModel Code)

The DB folder consists of the SQLite settings. The code for the DbSettings.java class is given below:

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

package com.journaldev.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";

    }
}

The code for the FavouritesDbHelper.java class is given below:

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

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

}

Our database consists of three columns – ID, URL, DATE.

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

The code for Favourites.java class is given below:

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

package com.journaldev.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;
    }

}

The code for the FavouritesViewModel.java class is given below:

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

package com.journaldev.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.journaldev.androidviewmodel.db.DbSettings;
import com.journaldev.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.journaldev.com", (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);
        }
    }

}

In the above code, we add, remove and load the data from the SQLite Database.

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

In the getFavs() method we do a deep clone of the ArrayList.

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

The ViewModel is the data manager for our View. It supplies the data on demand.

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

The addFav() and removeFav() methods would get called from our Activity class for two operations:

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

  • Change the SQLite DB

    更改SQLite数据库
  • Change the data supplied to the ListView through ArrayList.

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

Thus every time the view requires something, our ViewModel has to do two calls. We'll see in a later tutorial, how using LiveData optimizes this.

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

The code for the activity_main.xml layout is given below:

下面给出了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>

The code for the list_item_row.xml is given below:

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>

The code for the MainActivity.java class is given below:

MainActivity.java类的代码如下:

package com.journaldev.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;
        }

    }
}

In the above code, we've used an ArrayAdapter class for our ListView.

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

In our FloatingActionButton, we create an AlertDialog with an EditText to add a new URL in our ListView.

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

ViewModel adds it to the SQLite and ArrayList.

ViewModel将其添加到SQLite和ArrayList。

There's an ImageButton defined for each list row in the XML. The android:onClick method over there, invokes the method deleteFav().

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

In the deleteFav we trigger two calls - One to the ViewModel to delete the record from SQLite and the other to the ArrayList. This is expensive. We'll see in a later tutorial how LiveData optimizes this.

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

Clicking the tvUrl would launch the URL directly into the browser since we'd set the android:autoLink="true"

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

Did you know? 你知道吗?
Adapters that extend the ArrayAdapter class must pass the layout resource through the constructors. Else it can lead to duplicate rows in the ListView.
扩展ArrayAdapter类的适配器必须通过构造函数传递布局资源。 否则,可能导致ListView中的行重复。

We've assigned id tags to the View Holder and views in the ListView Adapter.

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

The tag ids are defined in a file tags.xml in the values folder:

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

The output of the above application in action is given below:

android viewmodel app output

上面应用程序的输出如下:

This brings an end to this tutorial. ViewModel is a vital of part of Android MVVM. You can download the final AndroidViewModel project from the link below:

本教程到此结束。 ViewModel是Android MVVM的重要组成部分。 您可以从下面的链接下载最终的AndroidViewModel项目:

GitHub Repository. GitHub存储库中签出Android Studio项目代码。

翻译自: https://www.journaldev.com/21081/android-viewmodel

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值