نظرة على Android Architecture Components

يتناول المقال مجموعة من المكتبات المفيدة في تطوير تطبيقات الأندرويد LiveData-Room-ViewModel

Abdulrahman Hasan Aghaمنذ سنتين

بسم الله الرحمن الرحيم

أعلنت Google في مؤتمرها الأخير IO18 عن مجموعة من المكتبات التي تساعد مطور الأندرويد في بناء تطبيقه على أسس ممتازة وقابلة للتعديل مستقبلاً. تعمل هذه المكتبات على توحيد العمل في بناء التطبيقات لتصبح التطبيقات ذات طابع متشابه حتى يسهل على المطورين العمل على مشاريع والتعديل عليها مستقبلاً عند الحاجة. هذه المكتبات أطلقتها Google تحت مسمى JetPack. لمعرفة المزيد حول هذه المكتبات يمكنك زيارة الرابط التالي: هنا

سنقوم بهذا الدرس باستخدام بعض هذه المكتبات في بناء تطبيق بسيط لأخذ فكرة بسيطة عن طريقة عملها. المكتبات التي سيتم إستخدامها هي LiveDate - Room DB - ViewModel. من خلال الرابط السابق يمكنك معرفة المزيد عن طريقة عمل كل مكتبة وسيتم إستعراض بعض هذه المميزات خلال الشرح إختصاراً للوقت.

ماهو التطبيق الذي سنعمل عليه ؟ 

التطبيق سيقوم بإضافة Notes تحتوي على عنوان وعلى نص بسيط وعرضها داخل RecyclerView. التطبيق قد يبدو بسيطاً للغاية ولكن حتى يستنى لك فهم آلية عمل المكتبات كان من اللازم تطبيقها على تطبيق بسيط ومن ثم تطبيقها على تطبيقات وأفكار معقدة أكثر.

كيف سيكون شكل الـ Design Architecture الخاص بالتطبيق ؟

سنحاول بناء التطبيق الخاص بنا إعتماداً على هذا التصميم

 

نلاحظ بأن التطبيق ينقسم إلى ثلاث أقسام رئيسية:

1- Activites أو الـ Views: مجلد يحتوي على الواجهات الخاصة بالتطبيق.

1- ViewModel: مجلد يحتوي على ملفات الـ ViewModel الخاصة بالـ Activities. الفائدة الرئيسية من هذه الملفات هي فصل تخزين المعلومات المعروضة داخل الواجهات عن مكان عرضها ووضعها في مكان آخر. في أغلب الأحيان نقوم بحفظ الـ Arrays التي تحتوي على الـ Data المعروضة داخل الـ RecyclerView داخل الـ Activity class نفسه. مما يعني خسارة هذه الـ Data حال تعرض الـ Activity لأي تغير في الـ Lifecycle   الخاصة بها. فعلى سبيل المثال، قيامك بقلب الشاشة من وضع Portriat إلى وضع Landscape سيزيل تماماً الـ Arrays المخزنة ويقوم بإنشائها من جديد وهذا أمر غير محبذ خصوصاً لو كانت هذه الـ Data مرتبطة بـ API Call مما يعني قيامك بتحميل الـ Data في كل مرة يقوم المستخدم بتغيير وضع الشاشة. جاءت مكتبة ViewModel لتحل هذه المشكلة. ستقوم إبتداء من الآن بتخزين الـ Arrays و الـ Data المتعلقة بـ Activity معينة داخل ViewModel وبهذا لن تفقد الـ Data الخاصة بك في حال تم تغير حالة الـ Activity وإعادة إنشائها من جديد.

3- Model or Repository: مجلد يحتوي على الـ Database الخاصة بالتطبيق ككل. يفضل إنشاء Model خاص لكل Data مختلفة تقوم بعرضها داخل التطبيق.

بعد التعرف على الـ Architecture Desgin الخاص بالتطبيق .. لنبدأ في التطبيق ..

أولاً: قم بعمل مشروع جديد وسميه بأي إسم.

 

ثانياً: قم بإضافة هذه الـ Dependency إلى ملف الـ Gradle.build الخاص بمشروعك.

// ViewModel and LiveData
def lifecycle_version = "1.1.1"
implementation "android.arch.lifecycle:extensions:$lifecycle_version"
// Room
def room_version = "1.1.1-rc1"
implementation "android.arch.persistence.room:runtime:$room_version"
annotationProcessor "android.arch.persistence.room:compiler:$room_version"
  

 

ثالثاً: قم بتصميم الواجهات الخاصة بالـ Activities. بإمكانك إستخدام الواجهات الخاصة بي إختصاراً للوقت.

activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".Activities.MainActivity">

    <android.support.v7.widget.RecyclerView
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:id="@+id/recyclerview"
        />

    <android.support.design.widget.FloatingActionButton
        android:layout_width="50dp"
        android:layout_height="50dp"
        android:id="@+id/add_btn"
        android:layout_alignParentBottom="true"
        android:layout_alignParentRight="true"
        android:layout_marginBottom="15dp"
        android:layout_marginRight="15dp"
        android:src="@drawable/ic_add"
        app:backgroundTint="@color/colorPrimary"/>

</RelativeLayout>

 

الشكل النهائي

 

activity.add.xml

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    xmlns:app="http://schemas.android.com/apk/res-auto">

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="horizontal"
        android:layout_marginTop="15dp">

        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="Title"
            android:textColor="@color/colorPrimary"
            android:layout_marginRight="15dp"
            android:layout_marginLeft="10dp"
            android:textSize="20sp"/>

        <EditText
            android:layout_width="match_parent"
            android:layout_height="50dp"
            android:padding="5dp"
            android:layout_marginRight="15dp"
            android:background="@drawable/border"
            android:id="@+id/title"/>

    </LinearLayout>

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="vertical"
        android:layout_marginTop="100dp">

        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="Body"
            android:textColor="@color/colorPrimary"
            android:layout_marginRight="15dp"
            android:layout_marginLeft="10dp"
            android:textSize="20sp"/>

        <EditText
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_marginRight="15dp"
            android:lines="5"
            android:padding="10dp"
            android:background="@drawable/border"
            android:layout_marginLeft="10dp"
            android:gravity="start"
            android:id="@+id/body"/>

    </LinearLayout>

    <android.support.design.widget.FloatingActionButton
        android:layout_width="50dp"
        android:layout_height="50dp"
        android:id="@+id/add_btn"
        android:layout_alignParentBottom="true"
        android:layout_alignParentRight="true"
        android:layout_marginBottom="15dp"
        android:layout_marginRight="15dp"
        android:src="@drawable/ic_create"
        app:backgroundTint="@color/colorPrimary"/>

</RelativeLayout>

 

الشكل النهائي

 

row.xml -> recyclerview row

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:background="@drawable/border"
    android:padding="5dp"
    android:layout_margin="10dp">

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:textStyle="bold"
        android:textSize="20sp"
        android:text="test"
        android:layout_marginLeft="5dp"
        android:id="@+id/title"/>

    <TextView
        android:text="test"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_below="@id/title"
        android:textSize="15sp"
        android:layout_marginLeft="5dp"
        android:id="@+id/body"
        android:maxLines="1"
        android:ellipsize="end"
        />

</RelativeLayout>

 

الشكل النهائي 

 

بعد الإنتهاء من التصاميم. ننتقل إلى أكواد الجافا وسنبدأ بـ Model Module الخاص بالـ Database وتخزين الـ Data لعرضها لاحقاً.

في البداية سنقوم بإنشاء مجلد خاص بالـ Entities أو POJO Classes. بالنسبة لتطبيقنا سنحتاج إلى كلاس واحد فقط سنقوم بتسميته Item. كل Item يحتوي على ID, Title, Body. ولكن كيف يتم تطبيق هذا الكلام باستخدام Room DB ؟

أولاً: نقوم بإنشاء كلاس جديد ونسميه Item.

ثانياً: نقوم بإضافة annotation لتدل على أن هذا الكلاس عبارة عن POJO كلاس. هذه الـ annotation هي @Entitiy.

@Entity
public class Item {}

 

ثالثاً: نقوم بإضافة الـ Attributes التي نحتاجها لكل Item وهي ID, Title, Body. قمت بإضافة ID خاص لكل Item لشرح واحدة من المميزات التي تقدمها Room. بإمكانك تحديد الـ PrimaryKey الخاص بكل Entity عن طريق إضافة annotation أخرى. وهي @PrimaryKey. ومن ثم توكيل مهمة إنشاء هذا الـ ID إلى Room عن طريقة إضافة autoGenerate = true.

@Entity
public class Item {

    @PrimaryKey(autoGenerate = true)
    private int id ;
    private String title;
    private String body;

}

 

رابعاً وأخيراً: نقوم بإضافة Constructor بسيط بالإضافة إلى Getters and Setters. ليصبح الشكل النهائي لـ Item Class :

import android.arch.persistence.room.Entity;
import android.arch.persistence.room.PrimaryKey;

@Entity
public class Item {

    @PrimaryKey(autoGenerate = true)
    private int id ;
    private String title;
    private String body;

    public Item(String title, String body) {
        this.title = title;
        this.body = body;
    }

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getTitle() {
        return title;
    }

    public void setTitle(String title) {
        this.title = title;
    }

    public String getBody() {
        return body;
    }

    public void setBody(String body) {
        this.body = body;
    }
}

 

 

الآن سننتقل إلى مجلد آخر يحتوي على DAOs إختصاراً لـ Data Access Object.وهو عبارة عن Interface يستخدم لتعريف الـ Operations المراد تنفيذها على الـ Database الخاصة بك. بمعنى آخر سيتم إنشاء Method لكل Operation. فعلى سبيل المثال، إذا أردت تطبيق الـ Insert Operation الإعتيادية ستقوم بإنشاء Method خاصة لعمل هذا ونفس الأمر بالنسبة لباقي العمليات ( Delete .. etc ). ولكن ماذا عن الـ Queries ؟ وخصوصاً في حال وجود أكثر من Query. هنا توفر لك Room القدرة على كتابة SQL statements وإسنادها لـ Method معينة. لنلقي نظرة على الكود ...

import android.arch.lifecycle.LiveData;
import android.arch.persistence.room.Dao;
import android.arch.persistence.room.Delete;
import android.arch.persistence.room.Insert;
import android.arch.persistence.room.Query;

import com.example.asus.test.Model.Entities.Item;

import java.util.List;

@Dao
public interface ItemDao {

    @Insert
    void insert(Item item);

    @Delete
    void delete(Item item);

    @Query("SELECT * FROM Item")
    LiveData<List<Item>> getItems();

}

نستطيع ملاحظة الآتي:

1- يتم وضع @Dao بداية الـ Interface للدلالة على أن هذا الـ Interface يستخدم كـ Data Access Object. ( بفكرة مشابهة لفكرة الـ Entity )

2- العمليات الإعتيادية كـ Insert and delete وغيرها لا فيها إلى كتابة SQL statements. وإنما بإمكانك إختصار الوقت وإضافة Insert or Delete وغيرها.

3- بالنسبة للـ Queries فيمكنك إضافة @Query ومن ثم كتابة SQL statement لتدل على نوعية الـ Data المراد أخذها من الـ DB. نلاحظ بأن العبارة المكتوبة أعلى getItems تستخرج كل الـ Items من الـ DB.

4- توفر لك مكتبة Room ميزة ممتازة. إذ يمكنها إكتشاف الأخطاء في كتابة الـ SQL مباشرة. 

5- سنتطرق للحديث عن الـ LiveData المتواجدة في آخر ميثود فيما بعد. 

 

بعد إنشاء ملف الـ Dao والذي كما ذكرنا سابقاً يحدد العمليات المراد تنفيذها على الـ Database. سنحتاج الآن لإنشاء كلاس يمثل الـ Database المراد تخزين البيانات فيها.

نقوم بإنشاء كلاس جديد ( abstract ) ونسميه ItemDatabase على سبيل المثال.

import android.arch.persistence.room.Database;
import android.arch.persistence.room.RoomDatabase;

import com.example.asus.test.Model.DAO.ItemDao;
import com.example.asus.test.Model.Entities.Item;

@Database(entities = {Item.class}, version = 1)
public abstract class ItemDatabase extends RoomDatabase{

    public abstract ItemDao itemDao();

}

 نلاحظ الآتي:

1- نقوم بإضافة annotation جديدة تدل على أن هذا الكلاس عبارة عن Database كلاس.

2- نقوم بإضافة الـ Entities المراد تخزينها داخل هذه الـ Database. بإمكانك إضافة أكثر من Entities لأنها تأتي هنا بمعنى ( Table ). 

3- نقوم بتحديد الـ Version. ( ملاحظة : تغييرك للـ Version سيؤدي إلى خطأ في حال عدم وجود Migration .. يمكنك زيارة الرابط في بداية الدرس لمزيد من المعلومات) .

4- نقوم بعمل Extend لـ RoomDatabase.

5- نقوم بإنشاء ميثود ( abstract ) تقوم بتصدير Object من نوع ItemDao لمعرفة وتحديد العمليات كما أسلفنا سابقاً.

 

بعد الإنتهاء من الـ DB الخاصة بنا، سنقوم بإنشاء Layer تبسط على الـ Modules التواصل مع الـ DB وأخذ ما يلزم عن طريق Interface بسيط. ( وجود هذه الـ Layer مهم جداً لأنه في حال عدم تواجده ستكون الـ DB منتشرة للكل وتستطيع جميع الـ Components التغيير والتعديل على الـ DB. فهي أيضاً مهمة من أجل حماية الـ DB. وتعتبر أيضاً طريقة مفيدة لتحسين الـ Architecture Design الخاص بتطبيقك ).

نقوم بإنشاء Interface خاص بالـ Repository Module. ( سيتم إستخدامه من باقي الـ Components كما ذكرنا سابقاً ).

import android.arch.lifecycle.LiveData;
import com.example.asus.test.Model.Entities.Item;
import java.util.List;

public interface Repository {

    void addItem(Item item);
    void deleteItem(Item item);
    LiveData<List<Item>> getItems();

}

 

 ثم نقوم بإنشاء كلاس RepositoryImpl والذي يعمل كحلقة وصل بين باقي الـ Components وبين الـ DB.

import android.arch.lifecycle.LiveData;
import android.arch.persistence.room.Room;
import android.os.AsyncTask;
import android.util.Log;

import com.example.asus.test.Application.MyApplication;
import com.example.asus.test.Model.Database.ItemDatabase;
import com.example.asus.test.Model.Entities.Item;

import java.util.List;


public class RepositoryImpl implements Repository{

    private static RepositoryImpl repository = new RepositoryImpl();
    private ItemDatabase itemDB;
    private final String TAG = this.getClass().getName();

    // operations constants
    private static final int INSERT_OPERATION = 0;
    private static final int DELETE_OPERATION = 1;

    // constructor
    private RepositoryImpl(){
        initDB();
    }

    public static Repository getInstance(){
        return repository;
    }

    private void initDB() {
        Log.e(TAG, "_DataBaseInit");
        itemDB = Room.databaseBuilder(MyApplication.getAppContext(),
                ItemDatabase.class, "ItemDatabase").build();
    }

    @Override
    public void addItem(Item item) {
        Log.e(TAG, "_ItemIsAddedToDB");
        new DataBaseOperation(item,INSERT_OPERATION).execute();
    }

    @Override
    public void deleteItem(Item item) {
        Log.e(TAG, "_ItemIsDeletedFromDB");
        itemDB.itemDao().delete(item);
    }

    @Override
    public LiveData<List<Item>> getItems() {
        Log.e(TAG, "_GetItemsFromDB");
        return itemDB.itemDao().getItems();
    }

    // Async
    class DataBaseOperation extends AsyncTask<Void,Void,Void> {

        public Item item;
        public int operation;

        public DataBaseOperation(Item item, int operation){
            this.item = item;
            this.operation = operation;
        }

        @Override
        protected Void doInBackground(Void... voids) {
            Log.e(TAG, "_doInBackgroundInvoked");
            // INSERT operation
            if (operation == INSERT_OPERATION) itemDB.itemDao().insert(item);
            // else delete ...
            return null;
        }

    }

}

 

لنقم بشرح هذا الكلاس خطوة بخطوة.

1- الكلاس من نوع Singleton لأن جميع الكلاسات ستتواصل مع Repository واحدة فقط.

2- سنقوم بتعريف أوبجكت من نوع ItemDatabase للتواصل وتنفيذ العمليات داخل الـ Database.

3- سنقوم بعدها بتعريف عدة Constants وسنشرح فيما بعد الهدف منها.

4- نقوم بإضافة public static accessor للـ Singleton Object. 

5- نقوم بتعريف ميثود ( initDB) لإنشاء الـ Database وذلك بإستخدام Builder ميثود موجودة مسبقاً داخل Room library تطلب 3 مدخلات. الأول الـ Application context والثاني الكلاس الخاص بالـ Database والذي تم تعريفه مسبقاً والثالث إسم الـ Database.

6- نقوم بعمل Override لجميع الـ Methods الموجودة داخل الـ Interface. ( ملاحظة : عمليات الإضافة والحذف تتطلب العمل على Background thread وأي محاولة لتنفيذها على الـ main thread سيتم عمل throw لـ Exception ومنعك من قبل Room نفسها. ) هناك عدة طرق لحل هذه المشكلة ولكن وجدت أسهلها عمل Inner class يرث Async.

7- داخل هذا الكلاس سنقوم بتعريف الـ Item المراد إجراء عملية إضافته أو حذفه. وأيضاً Operation code لمعرفة ما إذا كنت ترغب بحذف أو إضافة هذا الـ item.

بهذه النقطة نكون قد إنتهينا بكل ما يتعلق بـ RoomDB وبـ Repository Layer.

 

ننتقل الآن إلى ViewModel.

كما ذكرنا سابقاً بأن الغرض الرئيسي من الـ ViewModel هو تخزين المعلومات المراد عرضها داخل الـ View وعزلها تماماً حتى لا تتأثر بأي Lifecycle change قد يطرأ على الـ View. 

نقوم بإنشاء كلاس جديد الغرض منه تخزين الـ Notes المراد عرضها داخل الـ Recyclerview في الـ main activity.

import android.arch.lifecycle.LiveData;
import android.arch.lifecycle.MutableLiveData;
import android.arch.lifecycle.ViewModel;
import android.util.Log;

import com.example.asus.test.Model.Entities.Item;
import com.example.asus.test.Model.Repository.RepositoryImpl;

import java.util.List;

public class MainActivityViewModel extends ViewModel {

    private final String TAG = this.getClass().getName();

    private LiveData<List<Item>> listItems;

    public LiveData<List<Item>> getListItems() {
        if (listItems == null){
            Log.e(TAG, "_ListItemsIsNULL");
            listItems = new MutableLiveData<List<Item>>();
            loadItemsFromRepository();
        }
        Log.e(TAG, "_ReturningFromViewModel");
        return listItems;
    }

    private void loadItemsFromRepository() {
        Log.e(TAG, "_LoadFromDB");
        listItems = RepositoryImpl.getInstance().getItems();
    }
}

 

من خلال الكود السابق نلاحظ الآتي:

1- نقوم بعمل Extend لـ ViewModel لعمل ViewModel خاص بنا.

2- نقوم بتعريف الـ List of items المراد حفظها داخل الـ ViewModel.

3- الآن ستقوم بالـ View بإستدعاء getListItems في كل مرة تحتاج عرض البيانات، لذلك سنقوم بإضافة عبارة شرطية بسيطة لمعرفة ما إذا كانت الـ List المراد إرسالها إلى الـ View قد تم إنشاؤها من قبل. في حالة الـ Null سنقوم بالذهاب إلى الـ Repository وإحضار البيانات وإرسالها إلى الـ View. في حال كانت الـ List موجودة مسبقاً سنقوم بإرسالها مباشرة دون الذهاب إلى الـ DB.

حان الوقت للكلام عن مكتبة LiveData والهدف منها. LiveData من الممكن وصفها بأنها Wrapper يقوم بالإلتفاف حول الـ Data لإضافة ميزة ممتازة وهي الـ OnChange Listener. في حال تغير حالة الـ Data ستقوم LiveData مباشرة بإرسال إشارة لكل الـ Observers وإعلامهم بحدوث تغيير للـ Data. طبعاً هذا التفسير بسيط جداً ولا يصف المكتبة بوصف دقيق .. لذلك للإستزادة أكثر يمكنك مراجعة الرابط في بداية الدرس.

 

أخيراً نقوم بكتابة الأكواد المطلوبة داخل الـ MainActivity.java

import android.arch.lifecycle.ViewModelProviders;
import android.content.Intent;
import android.os.Bundle;
import android.support.design.widget.FloatingActionButton;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.util.Log;
import android.view.View;

import com.example.asus.test.Activities.Adapter.RecyclerViewAdapter;
import com.example.asus.test.Model.Entities.Item;
import com.example.asus.test.R;
import com.example.asus.test.ViewModel.MainActivityViewModel;

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

public class MainActivity extends AppCompatActivity implements View.OnClickListener{

    private RecyclerView recyclerView;
    private FloatingActionButton fab;
    private RecyclerViewAdapter adapter;
    private List<Item> list;
    private MainActivityViewModel mViewModel;

    // TAG for Logging
    private final String TAG = this.getClass().getName();

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

        Log.e(TAG,"_ActivityInit");

        // init views and adapter
        recyclerView = (RecyclerView) findViewById(R.id.recyclerview);
        fab = (FloatingActionButton) findViewById(R.id.add_btn);
        fab.setOnClickListener(this);
        list = new ArrayList<>();
        adapter = new RecyclerViewAdapter(list);

        // set adapter and layout manager
        recyclerView.setLayoutManager(new LinearLayoutManager(this,LinearLayoutManager.VERTICAL, false));
        recyclerView.setAdapter(adapter);

        // set view model
        mViewModel = ViewModelProviders.of(this).get(MainActivityViewModel.class);
        mViewModel.getListItems().observe(this, items -> {
            // update DataSet
            Log.e(TAG,"_AdapterIsUpdatedFromViewModel");
            list.clear(); list.addAll(mViewModel.getListItems().getValue());
            adapter.notifyDataSetChanged();
        });
    }

    @Override
    public void onClick(View v) {
        // go to add activity
        Intent intent = new Intent(this, AddActivity.class);
        startActivity(intent);
    }
}

 

1- نقوم بإضافة المتغيرات ومن ضمنها Object من كلاس MainActivityViewModel.

2- نقوم أولاً بتعريف الـ Views الموجودة داخل ملف الـ XML.

3- نقوم بعمل setup للـ Recyclerview.

4- نقوم بعدها بعمل setup لأوبجكت الـ ViewModel من خلال تمرير الـ Context حتى يتسنى للـ ViewModel معرفة الـ Lifecycle status الخاصة بالـ Activity.

5- أخيراً نقوم بإضافة الـ Activity كـ Observer للـ List المراد عرضها.

 

بالنسبة لـ add activity. فلا نحتاج سوى التواصل مع الـ Repository وإضافة Note جديدة.

import android.os.Bundle;
import android.support.annotation.Nullable;
import android.support.design.widget.FloatingActionButton;
import android.support.v7.app.AppCompatActivity;
import android.util.Log;
import android.view.View;
import android.widget.EditText;
import android.widget.Toast;

import com.example.asus.test.Model.Entities.Item;
import com.example.asus.test.Model.Repository.RepositoryImpl;
import com.example.asus.test.R;

public class AddActivity extends AppCompatActivity implements View.OnClickListener{

    private EditText title;
    private EditText body;
    private FloatingActionButton fab;

    private final String TAG = this.getClass().getName();


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

        Log.e(TAG,"_ActivityInit");

        // init vars
        title = (EditText) findViewById(R.id.title);
        body = (EditText) findViewById(R.id.body);
        fab = (FloatingActionButton) findViewById(R.id.add_btn);

        // set listener to FAB
        fab.setOnClickListener(this);
    }

    @Override
    public void onClick(View v) {
        Log.e(TAG, "_onClickInvoked");
        RepositoryImpl.getInstance().addItem(new Item(title.getText().toString(), body.getText().toString()));
        // show toast
        Toast.makeText(AddActivity.this,"Record Added", Toast.LENGTH_SHORT).show();
        // get back to main activity
        finish();
    }
}

 

المخرج النهائي للتطبيق :

 

نلاحظ عند تغيير وضعية الهاتف من Portrait إلى Landscape سيتم جلب المعلومات بشكل مباشرة من الـ ViewModel بدون الذهاب إلى الـ Repository. ( الجزء الأحمر الثاني )

 

 

إلى هنا نكون قد إنتهينا من الموضوع. نلقاكم في مواضيع أندرويدية أخرى :)

8
إعجاب
5705
مشاهدات
1
مشاركة
7
متابع
متميز
محتوى رهيب

التعليقات (2)

MuteeAljabri: ممتاز يعطيك العافية
ahmed saber:

ممكن تحط كود المشروع ؟

لايوجد لديك حساب في عالم البرمجة؟

تحب تنضم لعالم البرمجة؟ وتنشئ عالمك الخاص، تنشر المقالات، الدورات، تشارك المبرمجين وتساعد الآخرين، اشترك الآن بخطوات يسيرة !