آموزش LiveData در اندروید استودیو

دسته بندی: برنامه نویسی
سطح مقاله: متوسط
زمان مطالعه: 35 دقیقه
۳۱ تیر ۱۳۹۹

گوگل به منظور کمک به توسعه دهندگان اندروید و برای اینکه توسعه دهندگان برنامه‌های قوی تر، قابل آزمایش و قابل اطمینان‌تری را طراحی کنند، مجموعه ای از کتابخانه‌های "Android Architecture Components" را منتشر کرد، قابل توجه‌ترین کلاس‌ LiveData و کلاس‌های مربوط به lifecycle-aware، کتابخانه‌ی Room persistence و کتابخانه‌ی paging است. در این مقاله ما LiveData در اندروید و مشکلی که می‌توانید با آن حل کنید، را بررسی می‌کنیم. در ادامه همراه ما باشید.

فهرست محتوای این مقاله

LiveData چیست ؟

LiveData در اندروید یک کلاسی هست، که دارنده‌ی دیتای قابل مشاهده است. بر خلاف یک مشاهده‌ی منظم، LiveData  از چرخه‌ی حیات آگاه (lifecycle-aware) است، به این معنی که به چرخه‌ی حیات سایر مؤلفه‌های (components) برنامه مانند "Activities" ، "Fragments" یا "Services" دقت کرده و احترام می‌گذارد. این آگاهی تضمین می‌کند که LiveData فقط app component observers را که در حالت فعال چرخه (active lifecycle state) هستند، بروز می‌کند.

چرخه حیات اکتیویتی را در این تصویر مشاهده نمایید (Activity LifeCycle)

چرخه حیات فرگمنت را در این تصویر مشاهده نمایید(Fragment LifeCycle)

آموزش LiveData در اندروید استودیو

LiveData چگونه کار می‌کند ؟

LiveData یک ناظر را که توسط کلاس Observer نشان داده می‌شود، در نظر می‌گیرد، اگر چرخه‌ی حیات (lifecycle) آن در حالت STARTED یا RESUMED باشد، در وضعیت فعال قرار دارد. LiveData فقط ناظران (observers) فعال را درباره‌ی بروزرسانی‌ها آگاه می‌کند. ناظران غیرفعال که برای تماشای اشیاء LiveData ثبت شده اند، از تغییرات مطلع نمی‌شوند.
شما می‌توانید یک observer را با یک شیء که رابط LifecycleOwner را پیاده سازی کرده، ثبت کنید. این رابطه اجازه می‌دهد تا وقتی وضعیت Lifecycle object مربوط به DESTROYED تغییر می‌کند، observer از بین برود. این به ویژه برای "Activites" و "Fragments" مفید است، زیرا می‌توانند اشیاء LiveData را با خیال راحت مشاهده کنند، و نگران leaks_activities  و Fragment نباشند، با Unsubscribe کردن چرخه‌ی حیات (LifeCycle) آن‌ها سریع از بین می‌رود.
آموزش LiveData در اندروید استودیو

مزایای استفاده از LiveData

استفاده از LiveData مزایای زیر را ارائه می‌دهد:

اطمینان می‌دهد UI شما مطابق با وضعیت داده شما است:

LiveData از observer pattern پیروی می‌کند. LiveData با تغییر وضعیت LifeCycle، اشیاء Observer را مطلع می‌کند. می‌توانید    کد خود را برای به روزرسانی UI در اشیاء Observer تلفیق کنید. به جای بروز کردن UI هر بار که داده‌های برنامه تغییر می‌کنند، Observer شما می‌تواند، هر زمان که تغییر ایجاد می‌کند، رابط کاربر را بروز کند.

No Memory Leak:

Observerها به LifeCycle Object محدود می‌شوند و بعد از نابودی (destroyed) چرخه‌ی حیات مرتبط با آن‌ها، خود را پاک می‌کنند.
به دلیل Stop شدن activities، هیچ crashes وجود ندارد:

اگر چرخه حیات Observer غیرفعال باشد، مانند Activity در back stack، هیچ رویدادی را از LiveData دریافت نمی‌کند.

Handle کردن LifeCycle خیلی راحت شده و دیگر به صورت دستی نمی‌باشد:

مؤلفه‌های UI فقط داده‌های مربوطه را مشاهده می‌کنند، و مشاهده، متوقف یا دوباره از سر گرفته نمی‌شوند. LiveData به طور خودکار تمام این موارد را مدیریت می‌کند، زیرا Observer از تغییرات وضعیت چرخه‌ی حیات مربوطه آگاه است.

اطلاعات همیشه بروز است:

اگر چرخه‌ی حیات یا همان LifeCycle غیرفعال شود، پس از فعال شدن دوباره آخرین اطلاعات را دریافت می‌کند. به عنوان مثال،          Activity که در Background بود، آخرین اطلاعات را بلافاصله پس از بازگشت به Foreground دریافت می‌کند.

پیکربندی  به صورت مناسب تغییر می‌کند:

اگر یک Activity یا Fragment به دلیل تغییر پیکربندی، مانند چرخش دستگاه (rotation)، دوباره بازیابی شود، بلافاصله آخرین اطلاعات موجود را دریافت می‌کند.

به اشتراک گذاری منابع:

می توانید با استفاده از الگوی singleton، یک شی LiveData را برای بسته بندی خدمات سیستم گسترش دهید، تا در برنامه‌ی شما به اشتراک گذاشته شود. شی LiveData یک بار به سرویس سیستم متصل می‌شود، و سپس هر ناظر که به این منبع نیاز داشته باشد، می‌تواند فقط شیء LiveData را تماشا کند.

LiveData در اندروید در مقایسه با RxJava

LiveData در اندروید تا حدودی شبیه RxJava است، به جز اینکه LiveData از چرخه‌ی حیات آگاه یا lifecycle aware است.

اگر View در پس زمینه (Background) باشد، داده شما در View بروز نمی‌شود. این به ما کمک می‌کند تا از exceptions مانند IllegalStateException و غیره جلوگیری کنیم.

LiveData چگونه در ViewModel اکتیویتی را بروز می‌کند؟

هنگامی که Observer را در َActivity خود ثبت می‌کنیم، باید متد ()onChanged  را override کنیم. هر زمان که LiveData تغییر کند، متد ()onChanged شروع به کار می‌کند. بنابراین در متد ()onChanged می‌توانیم LiveData تغییر یافته را روی View بروز کنیم.
LiveData فقط یک نوع داده است، که هر زمان داده‌ها تغییر می‌کنند، به ناظر آن اطلاع می‌دهند. LiveData مانند یک اعلام کننده تغییر داده است.
LiveData با استفاده از ()setValue و ()postValue به Observer اطلاع می‌دهد.
()setValue  روی Thread اصلی (Main Thread) اجرا می‌شود.
()postValue روی Thread پس زمینه (Background Thread)  اجرا می‌شود.
با مراجعه به ()getValue در نمونه LiveData اطلاعات فعلی را به شما باز می‌گرداند.

MutableLiveData

MutableLiveData فقط یک کلاس است که کلاس نوع LiveData را گسترش می‌دهد.
MutableLiveData معمولاً مورد استفاده قرار می‌گیرد، زیرا متدهای ()postValue() ،setValue  را به صورت عمومی ارائه می‌دهد،       چیزی که کلاس LiveData ارائه نمی‌دهد.
LiveData / MutableLiveData معمولاً در بروزرسانی داده‌ها در RecyclerView از نوع مجموعه (لیست، ArrayList و غیره)
استفاده می‌شود.
در بخش بعد، برنامه‌ای ایجاد خواهیم کرد، که ردیف هایی را در RecyclerView از بانک اطلاعات SQLite اضافه یا حذف می‌کند.
ما از MutableLiveData استفاده خواهیم کرد، که سوابق RecyclerView را هر زمان که LiveData تغییر کند، بروز می‌کند.

مثالی از 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 Code

کد طرح 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.sevenlearn.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";

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


package com.journaldev.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.sevenlearn.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.sevenlearn.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.journaldev.androidlivedata.db.DbSettings;
import com.journaldev.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 لیستی از Favourite instance objects را در خود نگه می‌دارد. در ()addFav و ()removeFav تغییرات داده را به Observer اعلام می‌کنیم، که در MainActivity تعریف شده است.
ما برای مقایسه‌ی نسخه قدیمی و جدید، یک نسخه از ArrayList ایجاد می‌کنیم.
کد کلاس MainActivity.java در زیر آورده شده است:
package com.sevenlearn.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);
                    }
                });
            }
        }
    }

}

در کد بالا، ما کلاس Adapter را  برای RecyclerView در Activity تعریف کرده‌ایم.

;(mFavViewModel.getFavs().observe(this, favsObserver برای تنظیم Observer در MainActivity استفاده می‌شود، که هر زمان که LiveData بروز شود، از کلاس ViewModel مطلع می‌شود.
anonymous کلاس favsObserver متشکل از متد ()onChanged است، و آخرین داده هایی را ارائه می‌دهد، که سپس از طریق RecyclerView بروز می‌شوند.
خروجی برنامه‌ی فوق در زیر آورده شده است و می‌توانید مشاهده کنید. (از این لینک می‌توانید برنامه را دانلود کنید)
آموزش LiveData در اندروید استودیو
جمع‌بندی:
آشنایی با LiveData می‌تواند خیلی مفید باشد، در این مقاله به توضیح کارایی و ویژگی‌های LiveData پرداختیم، به دلیل اینکه LiveData از چرخه‌ی حیات آگاه هست، از خیلی ایراد‌های برنامه مثل ارور یا memory leak و ... که بالا اشاره کردیم، جلوگیری می‌کند. اگر در این مورد سوال یا نظری داشتید خوشحال می‌شویم که با ما و کاربران سون لرن به اشتراک بگذارید.
منابع:
اگر به یادگیری بیشتر در زمینه‌ی اندروید علاقه داری، با شرکت در دوره‌ی آموزشی متخصص اندروید در کمتر از یکسال به یک توسعه‌دهنده اندروید همه فن حریف تبدیل می‌شوی که آماده‌ی استخدام، دریافت پروژه و حتی پیاده‌سازی اپلیکیشن خودت هستی.
چه امتیازی به این مقاله می دید؟

نظرات کاربران

امیر

با وجود livedata چرا باید از rxjava استفاده کرد؟

امیر حسین حیدری

با سلام، این بستگی به پروژه و خود شما داره میتونین از هر کدوم که خواستین استفاده کنین، هر کدوم مزیت های خودشون رو دارن و من به شخصه از RxJava استفاده میکنم

پوریا شفیعی سروستانی

عالی بود
سپاس از امیر عزیز

امیر حسین حیدری

سلام ممنون از نظرتون موفق باشید.

ارسال دیدگاه
خوشحال میشیم دیدگاه و یا تجربیات خودتون رو با ما در میون بذارید :