معماری برنامه اندروید براساس MVP

سیده آمین ارمان

کاربر نگاه دانلود
کاربر نگاه دانلود
عضویت
2016/05/10
ارسالی ها
1,730
امتیاز واکنش
20,744
امتیاز
795
محل سکونت
البرز
اساسی ترین کار در علم کامپیوتر انتخاب مناسب الگوی معماری (Architecture Pattern) پروژه است. تنها راهی است که میتوان پروژه را براساس آن تمیز (clean)، گسترش پذیر (expansible) و قابل تست (testable) نگهداری کرد. الگوها روش های شناخته شده ای هستند که در طول سال های برنامه نویسی بوجود آمده اند، تست شده اند، بهینه شده اند و امروزه بعنوان استاندارد شناخته میشوند. آنها بطور مدارم درحال متحول شدن هستند. بعنوان نمونه در اندروید الگوی معتبر (Model-View-Controller (MVC به الگوی (Model-View-Presenter (MVP تغییر کرده است. در بخش اول مقاله به تفاوت ها و ویژگیهای هرکدام از این الگوها میپردازیم.


Android SDK
وقتی به جزییات Android SDK و بطور خاص به layout – activity – data دقیق شویم، متوجه میشویم که بهترین الگو برای استفاده در معماری برنامه اندروید مدل (Model View Controller (MVC است. اما زمانیکه پروژه سنگین و بزرگ میشود، امکانات آن علی الخصوص در مورد تست برنامه جوابگوی مشکلات پیش رو نیست (برای اطلاعات بیشتر رجوع شود به Separation of concerns).

با اینحال، هنوز براساس معماری اندروید، امکان انتخاب الگوی دیگر وجود دارد که به آن بدون الگو! یا آنتی پترن (Anti-pattern) گفته میشود. با اینکه MVC الگویی قابل اعتماد و شناخته شده است، اما زمین بازی را به الگوی جدیدتر MVP باخته است چراکه این الگو ویژگیهای خاصی را به همراه آورده است که بخوبی تحت Separation of concerns تعریف میشود.

در معماری برنامه از MVC استفاده کنیم یا MVP؟
جواب دقیقی به این سوال نمیشود داد. گروهی از برنامه نویسان همچنان به MVC اعتقاد دارند، گروهی شیفته MVP هستند و گروهی – مانند اینجانب – به الگوی MVVM علاقه مند هستند. هرکدام از این الگوها ویژگیها و معایب مخصوص به خود را دارند. که بدین ترتیب برای جواب دادن به سوال فوق باید جزییات همه این الگوها را دانست تا با چشم باز و براساس نیاز پروژه بهترین الگو انتخاب شود. براساس تعریف وایکی پدیا:

Model–view–controller (MVC) is a software architectural pattern mostly (but not exclusively) for implementing user interfaces on computers. It divides a given software application into three interconnected parts, so as to separate internal representations of information from the ways that information is presented to or accepted from the user.

Model–view–presenter (MVP) is a derivation of the Model–View–Controller (MVC) architectural pattern, and is used mostly for building user interfaces. In MVP the presenter assumes the functionality of the “middle-man”. In MVP, all presentation logic is pushed to the presenter.

تفاوت های بین MVC و MVP
ویژگیهای الگوی MVP:

ارتباط بین View با Model قطع شده است.
ساخت Unit Test راحت شده است.
عموما رابـ ـطه یک به یک بین Presenter و View وجود دارد که این ارتباط به یک یه چند برای پروژه های سنگین و بزرگتر هم قابل گسترش است. بدین ترتیب برای Viewهای بزرگ میتوان چندین Presenter معرفی کرد.
ویژگیهای الگوی MVC:

Controllerها رفتارگرا (behavior based) هستند و میتوانند شامل چندید View باشند.
View مستقیما با Model در ارتباط است.
MVC-vs-MVP.png


MVC vs MVP

Model View Presenter در اندروید
جداسازی المانها در اندروید بخوبی تعریف نشده است. بعنوان نمونه اکتیویتی ها رایطه بسیار نزدیکی با Model و View دارند. همانگونه که اشاره شد برای نگهداشتن پروژه در بهترین حالت – گسترش پذید، تمیز و قابل تست شده -، بسیار مهم است که جداسازی المانها در آن رعایت شده باشد و این چیزی است که الگوی MVP در اختیار ما قرار میدهد.

MVP.png


بهترین راه برای اجرای MVP
روشهای زیادی برای اجرای MVP وجود دارد که بدین ترتیب به طرق مختلف میتوان پروژه اندروید را بر آن منطبق کرد. این تنوع در اجرا بخاطر تفاوت در نقش هایی است که Presenter بازی میکند. اما صرف نظر از این روشها، هسته اصلی MVP باید یکسان باشد.

Presenter: رابطی است میان View و Model. دیتا را از مدل دریافت میکند، آنرا ویرایش/آماده میکند و در اختیار View قرار میدهد. اما برخلاف MVC، تصمیم میگیرد که چه واکنشی به درخواست کاربر در ارتباط با View انجام دهد (مثلا زمانیکه دکمه ای کلیک شد).
View: عموما توسط اکتیوتی اجرا میشود و رفرنسی از Presenter را در خود دارد. تنها کاری که View انجام میدهد این است که متدخاصی را – از Presenter – زمانیکه اتفاقی در واسط کاربری افتاد (مثلا زمانیکه دکمه ای کلیک شد)، صدا کند.
Model: در برنامه ای که خیلی خوب لایه های معماری رعایت شده باشد، Model تنها چیزی است که منطق بیزینسی پروژه را اجرا میکند. اینطوری به آن نگاه کنید که دیتای مورد نیاز برای نماش داده شدن در View را دراختیارمان قرار میدهد.
تعاریف فوق از مطلب MVP for Android: how to organize the presentation layer برداشت و ترجمه شده است.




 
  • پیشنهادات
  • سیده آمین ارمان

    کاربر نگاه دانلود
    کاربر نگاه دانلود
    عضویت
    2016/05/10
    ارسالی ها
    1,730
    امتیاز واکنش
    20,744
    امتیاز
    795
    محل سکونت
    البرز
    در بخش اول این مطلب، راجع به مفهوم (Model-View-Presenter (MVP و علت مهم بودن آن در توسعه برنامه اندروید توضیح دادیم. در این بخش به سراغ کدنویسی میرویم تا عمیقتر آنرا مورد بررسی قرار دهیم. پروژه ای ساده خواهیم ساخت و طریقه ساخت لایه های مختلف و چگونگی ارتباط بین این لایه ها را خواهیم دید. ممکن است پیچیده بنظر برسد، اما بعد از درک کامل آن خواهید دید که چقدر مفید است.

    یادآوری از بخش اول:

    • Presenter: رابطی است میان View و Model. دیتا را از مدل دریافت میکند، آنرا ویرایش/آماده میکند و در اختیار View قرار میدهد. اما برخلاف MVC، تصمیم میگیرد که چه واکنشی به درخواست کاربر در ارتباط با View انجام دهد (مثلا زمانیکه دکمه ای کلیک شد).
    • View: عموما توسط اکتیوتی اجرا میشود و رفرنسی از Presenter را در خود دارد. تنها کاری که View انجام میدهد این است که متدخاصی را – از Presenter – زمانیکه اتفاقی در واسط کاربری افتاد (مثلا زمانیکه دکمه ای کلیک شد)، صدا کند.
    • Model: در برنامه ای که خیلی خوب لایه های معماری رعایت شده باشد، Model تنها چیزی است که منطق بیزینسی پروژه را اجرا میکند. اینطوری به آن نگاه کنید که دیتای مورد نیاز برای نماش داده شدن در View را دراختیارمان قرار میدهد.
    هدف نهایی از معماری مبتنی بر MVP افزایش هرچه بیشتر جداسازی لایه های پروژه است (separation of concerns). بنابراین باید از جدا بودن (isolation) لایه های Model, View و Controller مطمین باشیم. بنابراین براساس تعریف MVP، لایه های View و Model نباید به هیچ طریقی باهم ارتباط برقرار کنند. بنابراین تمام ارتباطات باید از طریق لایه Presentation برقرار شود.
    نمودار عمل (Action Diagram) بین لایه ها
    خب، بیایید یک برنامه بسیار ساده را درنظر بگیریم که به کاربر اجازه میدهد تا نوشته ای را منتشر کند. کاربر متنی را وارد میکند، برنامه هم آنرا ذخیره میکند و سپس نشان میدهد. با فرض اینکه برنامه براساس معماری MVP نوشته شده است، مراحل انجام کار بصورت نمودار زیر خواهد بود.



    MVP_ActionDiagram.png
    Model View Presenter (MVP) action diagram


    چیزی که نمودار میگوید این است که:

    1. کاربر بر روی دکمه “insert note” کلیک میکند. View متن کاربر را به Presenter ارسال میکند.
    2. Presenter، یک آبجکت جدید از Note با استفاده از متن داده شده می سازد. سپس Model را فرامیخواند و آبجکت ساخته شده را به متد مدل تحویل میدهد تا مدل آنرا در دیتابیس ذخیره کند.
    3. Model، آبجکت را در دیتابیس ذخیره میکند و با استفاده از CallBack، با/بدون موفقیت انجام شدن ترنزکشن را به اطلاع Presenter میرساند.
    4. Presenter، نتیجه را دریافت میکند و براساس آن به View میگوید که چه چیزی را به کاربر نشان دهد.
    نمودار بالا اطلاعات خیلی خوبی به ما میدهد که قرار است در کد چطور عمل کنیم. طریقه برقرار کردن ارتباط بین لایه ها به روش های مختلف انجام میشود. یک روش انتقال مستقیم داده به آبجکت است (direct object access)، یک روش استفاده از اینترفیس است، روش دیگر استفاده از یک نوع EventBus است. در این مطلب برای افزایش جداسازی (isolation) از روش دوم که استفاده از اینترفیس است، استفاده میکنیم. همچنین استفاده از اینترفیس روش توصیه شده از طرف اندروید است.

    نمودار کلاس (Class Diagram) بین لایه ها
    حالا بیایید با استفاده از نمودار عمل بالا به ساخت نمودار کلاس بپردازیم. فقط تغییر کوچکی در نمودار بالا میدهیم و بجای استفاده از CallBack برای ارسال نتیجه از Presenter به Model، از اینترفیس استفاده میکنیم. استفاده از اینترفیس کیفیمت کار بالاتر میبرد اگرچه بعضی ها ممکن است ادعا کنند، استفاده از CallBack میزان جداسازی (isolation of concerns) را بالاتر میبرد.



    MVP_ClassDiagram.png
    Model View Presenter Class Diagram


    1. Presenter، متدهای تعریف شده در اینترفیس PresenterOps را اجرا (implement) میکند.
    2. View رفرنسی از PresenterOps میگیرد تا به Presenter دسترسی پیدا کند.
    3. Model، متدهای ModelOps را اجرا(implement) میکند.
    4. Presenter رفرنسی از ModelOps میگیرد تا به Model دسترسی پیدا کند.
    5. Presenter، متدهای RequiredPresenterOps را اجرا (implement) میکند.
    6. Model رفرنسی از RequiredPresenterOps میگیرد تا به Presenter دسترسی پیدا کند.
    7. View، متدهای RequiredViewOps را اجرا (implement) میکند.
    8. Presenter رفرنسی از RequiredViewOps میگیرد تا به View دسترسی پیدا کند.
    اجرا کردن MVP در برنامه اندروید
    خیلی تا حالا داستان! تعریف کردیم، خب، یک راست برویم سراغ کد. برای بهتر سازمان دادن بکار از اینترفیس ها را داخل کلاسها قرار داده ام تا فهم کار آسانتر شود. از آنجاییکه اجرای MVP باندازه کافی پیچیدگی دارد، هیچ متدی را خارج از MVP تعریف نمیکنیم. فرض را بر این میگذارم که شما بخوبی با AndroidSDK آشنا هستید.



    Please, ورود or عضویت to view URLs content!

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    /*
    * Aggregates all communication operations between MVP pattern layer:
    * Model, View and Presenter
    */
    public interface MainMVP {

    /**
    * View mandatory methods. Available to Presenter
    * Presenter -> View
    */
    interface RequiredViewOps {
    void showToast(String msg);
    void showAlert(String msg);
    // any other ops
    }

    /**
    * Operations offered from Presenter to View
    * View -> Presenter
    */
    interface PresenterOps{
    void onConfigurationChanged(RequiredViewOps view);
    void onDestroy(boolean isChangingConfig);
    void novaNota(String textoNota);
    void deletaNota(Nota nota);
    // any other ops to be called from View
    }

    /**
    * Operations offered from Presenter to Model
    * Model -> Presenter
    */
    interface RequiredPresenterOps {
    void onNotaInserida(Nota novaNota);
    void onNotaRemovida(Nota notaRemovida);
    void onError(String errorMsg);
    // Any other returning operation Model -> Presenter
    }

    /**
    * Model operations offered to Presenter
    * Presenter -> Model
    */
    interface ModelOps {
    void insereNota(Nota nota);
    void removeNota(Nota nota);
    void onDestroy();
    // Any other data operation
    }
    }


    Please, ورود or عضویت to view URLs content!

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    public class MainPresenter
    implements MainMVP.RequiredPresenterOps, MainMVP.PresenterOps {

    // Layer View reference
    private WeakReference mView;
    // Layer Model reference
    private MainMVP.ModelOps mModel;

    // Configuration change state
    private boolean mIsChangingConfig;

    public MainPresenter(MainMVP.RequiredViewOps mView) {
    this.mView = new WeakReference<>(mView);
    this.mModel = new MainModel(this);
    }

    /**
    * Sent from Activity after a configuration changes
    * @param view View reference
    */
    @Override
    public void onConfigurationChanged(MainMVP.RequiredViewOps view) {
    mView = new WeakReference<>(view);
    }

    /**
    * Receives {@link MainActivity#onDestroy()} event
    * @param isChangingConfig Config change state
    */
    @Override
    public void onDestroy(boolean isChangingConfig) {
    mView = null;
    mIsChangingConfig = isChangingConfig;
    if ( !isChangingConfig ) {
    mModel.onDestroy();
    }
    }

    /**
    * Called by user interaction from {@link MainActivity}
    * creates a new Note
    */
    @Override
    public void newNote(String noteText) {
    Note note = new Note();
    note.setText(textoNota);
    note.setDate(getDate());
    mModel.insertNote(note);
    }

    /**
    * Called from {@link MainActivity},
    * Removes a Note
    */
    @Override
    public void removeNote(Note note) {
    mModel.removeNote(note);
    }

    /**
    * Called from {@link MainModel}
    * when a Note is inserted successfully
    */
    @Override
    public void onNoteInsert(Note newNote) {
    mView.get().showToast("New register added at " + newNote.getDate());
    }

    /**
    * Receives call from {@link MainModel}
    * when Note is removed
    */
    @Override
    public void onNoteRemoved(Note noteRemoved) {
    mView.get().showToast("Note removed);
    }

    /**
    * receive errors
    */
    @Override
    public void onError(String errorMsg) {
    mView.get().showAlert(errorMsg);
    }
    }


    Please, ورود or عضویت to view URLs content!

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    public class MainModel
    implements MainMVP.ModelOps {

    // Presenter reference
    private MainMVP.RequiredPresenterOps mPresenter;

    public MainModel(MainMVP.RequiredPresenterOps mPresenter) {
    this.mPresenter = mPresenter;
    }

    /**
    * Sent from {@link MainPresenter#onDestroy(boolean)}
    * Should stop/kill operations that could be running
    * and aren't needed anymore
    */
    @Override
    public void onDestroy() {
    // destroying actions
    }

    // Insert Note in DB
    @Override
    public void insertNote(Note note) {
    // data business logic
    // ...
    mPresenter.onNoteInserted(note);
    }

    // Removes Note from DB
    @Override
    public void removeNote(Note note) {
    // data business logic
    // ...
    mPresenter.onNoteRemoved(note);
    }
    }
    اتصال کلاس ها به اندروید
    در MVP، لایه View مسوول ساخت لایه Presenter است که این لایه هم به نوبه خود مسوول ساخت آبجکت View است. با توجه به اینکه در اندروید اکتیویتی نقش View را بازی میکند، باید خصوصیات اکتیویتی و بطور خاص
    Please, ورود or عضویت to view URLs content!
    آنرا مد نظر قرار دهیم (اینکه اکتیویتی در هر زمان ممکن است ساخته/نابود شود که به طریق اولی آبجکت های داخلش نیز متاثر خواهند شد). بنابراین احتیاج به المان چهارمی داریم، StateMaintainer، که مسوولیت کنترل وضعیت Presenter و View در طی تغییر چرخه حیات اکتیویتی را دارا باشد. نمودار ساده MVP که متاثر از چرخه حیات اکتیویتی است را در زیر مشاهده میکنید.



    MVP_Activity_Lifecycle.png
    MVP Objects destruction and reconstruction during Activity lifecycle changes


    1. اکتیویتی، آبجکتی از Presenter میسازد و PresenterOps را ذخیره میکند. Presenter هم در StateMaintainer ذخیره میشود.
    2. Presenter، در طی ساخت خود، RequiredViewOps را دریافت میکند و آبجکت Model را میسازد.
    3. مدل، RequiredPresenterOps را دریافت میکند.
    4. وقتی اکتیویتی درحال نابود شدن است، Presenter را آگاه میکند.
    5. Presenter، اطلاعات را پردازش میکند و Model را آگاه میکند و اطلاعات مورد نیاز مدل را به آن میدهد.
    6. اکتیویتی، Presenter را از StateMaintainer بازیابی میکند، RequiredViewOps را ارسال میکند و آنرا از وضعیتش آگاه میکند.
    کلاس StateMaintainer
    این اجرای کلاس StateMaintainer میتواند برای ذخیره وضعیت هر آبجکتی استفاده شود.

    Please, ورود or عضویت to view URLs content!

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    public class StateMaintainer {
    protected final String TAG = getClass().getSimpleName();

    private final String mStateMaintenerTag;
    private final WeakReference mFragmentManager;
    private StateMngFragment mStateMaintainerFrag;

    /**
    * Constructor
    * @param fragmentManager FragmentManager reference
    * @param stateMaintainerTAG the TAG used to insert the state maintainer fragment
    */
    public StateMaintainer(FragmentManager fragmentManager, String stateMaintainerTAG) {
    mFragmentManager = new WeakReference<>(fragmentManager);
    mStateMaintenerTag = stateMaintainerTAG;
    }

    /**
    * Create the state maintainer fragment
    * @return true: the frag was created for the first time
    * false: recovering the object
    */
    public boolean firstTimeIn() {
    try {
    // Recovering the reference
    mStateMaintainerFrag = (StateMngFragment)
    mFragmentManager.get().findFragmentByTag(mStateMaintenerTag);

    // Creating a new RetainedFragment
    if (mStateMaintainerFrag == null) {
    Log.d(TAG, "Creating a new RetainedFragment " + mStateMaintenerTag);
    mStateMaintainerFrag = new StateMngFragment();
    mFragmentManager.get().beginTransaction()
    .add(mStateMaintainerFrag, mStateMaintenerTag).commit();
    return true;
    } else {
    Log.d(TAG, "Returns a existent retained fragment existente " + mStateMaintenerTag);
    return false;
    }
    } catch (NullPointerException e) {
    Log.w(TAG, "Error firstTimeIn()");
    return false;
    }
    }


    /**
    * Insert Object to be preserved during configuration change
    * @param key Object's TAG reference
    * @param obj Object to maintain
    */
    public void put(String key, Object obj) {
    mStateMaintainerFrag.put(key, obj);
    }

    /**
    * Insert Object to be preserved during configuration change
    * Uses the Object's class name as a TAG reference
    * Should only be used one time by type class
    * @param obj Object to maintain
    */
    public void put(Object obj) {
    put(obj.getClass().getName(), obj);
    }


    /**
    * Recovers saved object
    * @param key TAG reference
    * @param Class type
    * @return Objects
    */
    @SuppressWarnings("unchecked")
    public T get(String key) {
    return mStateMaintainerFrag.get(key);

    }

    /**
    * Verify the object existence
    * @param key Obj TAG
    */
    public boolean hasKey(String key) {
    return mStateMaintainerFrag.get(key) != null;
    }


    /**
    * Save and manages objects that show be preserved
    * during configuration changes.
    */
    public static class StateMngFragment extends Fragment {
    private HashMap<String, Object> mData = new HashMap<>();

    @Override
    public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    // Grants that the frag will be preserved
    setRetainInstance(true);
    }

    /**
    * Insert objects
    * @param key reference TAG
    * @param obj Object to save
    */
    public void put(String key, Object obj) {
    mData.put(key, obj);
    }

    /**
    * Insert obj using class name as TAG
    * @param object obj to save
    */
    public void put(Object object) {
    put(object.getClass().getName(), object);
    }

    /**
    * Recover obj
    * @param key reference TAG
    * @param Class
    * @return Obj saved
    */
    @SuppressWarnings("unchecked")
    public T get(String key) {
    return (T) mData.get(key);
    }
    }
    }
    اکتیویتی اصلی (لایه View)


    Please, ورود or عضویت to view URLs content!

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    public class MainActivity extends AppCompatActivity
    implements MainMVP.RequiredViewOps {

    protected final String TAG = getClass().getSimpleName();

    // Responsible to maintain the Objects state
    // during changing configuration
    private final StateMaintainer mStateMaintainer =
    new StateMaintainer( this.getFragmentManager(), TAG );

    // Presenter operations
    private MainMVP.PresenterOps mPresenter;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    startMVPOps();
    setContentView(R.layout.activity_main);
    Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
    setSupportActionBar(toolbar);
    FloatingActionButton fab = (FloatingActionButton) findViewById(R.id.fab);
    }


    /**
    * Initialize and restart the Presenter.
    * This method should be called after {@link Activity#onCreate(Bundle)}
    */
    public void startMVPOps() {
    try {
    if ( mStateMaintainer.firstTimeIn() ) {
    Log.d(TAG, "onCreate() called for the first time");
    initialize(this);
    } else {
    Log.d(TAG, "onCreate() called more than once");
    reinitialize(this);
    }
    } catch ( InstantiationException | IllegalAccessException e ) {
    Log.d(TAG, "onCreate() " + e );
    throw new RuntimeException( e );
    }
    }


    /**
    * Initialize relevant MVP Objects.
    * Creates a Presenter instance, saves the presenter in {@link StateMaintainer}
    */
    private void initialize( MainMVP.RequiredViewOps view )
    throws InstantiationException, IllegalAccessException{
    mPresenter = new MainPresenter(view);
    mStateMaintainer.put(MainMVP.PresenterOps.class.getSimpleName(), mPresenter);
    }

    /**
    * Recovers Presenter and informs Presenter that occurred a config change.
    * If Presenter has been lost, recreates a instance
    */
    private void reinitialize( MainMVP.RequiredViewOps view)
    throws InstantiationException, IllegalAccessException {
    mPresenter = mStateMaintainer.get( MainMVP.PresenterOps.class.getSimpleName() );

    if ( mPresenter == null ) {
    Log.w(TAG, "recreating Presenter");
    initialize( view );
    } else {
    mPresenter.onConfigurationChanged( view );
    }
    }


    // Show AlertDialog
    @Override
    public void showAlert(String msg) {
    // show alert Box
    }

    // Show Toast
    @Override
    public void showToast(String msg) {
    Toast.makeText(getApplicationContext(), msg, Toast.LENGTH_SHORT).show;
    }
    }
    سخن آخر
    این مطلب خیلی طولانی شد، شرمنده! اما واقعا امیدوارم بدرد کسی بخورد. در قسمت آخر (بعدی)، طریقه استفاده از final framework را شرح میدهم که باعث ساده تر شده کار در استفاده از الگوی MVP میشود. همچنین به چند گره ای که باعث گیر کردن کار در استفاده از MVP میشود، اشاره خواهم کرد
     

    سیده آمین ارمان

    کاربر نگاه دانلود
    کاربر نگاه دانلود
    عضویت
    2016/05/10
    ارسالی ها
    1,730
    امتیاز واکنش
    20,744
    امتیاز
    795
    محل سکونت
    البرز
    در بخش اول این مطلب، راجع به مفهوم (Model-View-Presenter (MVP و علت مهم بودن آن در توسعه برنامه اندروید توضیح دادیم. در این بخش به سراغ کدنویسی میرویم تا عمیقتر آنرا مورد بررسی قرار دهیم. پروژه ای ساده خواهیم ساخت و طریقه ساخت لایه های مختلف و چگونگی ارتباط بین این لایه ها را خواهیم دید. ممکن است پیچیده بنظر برسد، اما بعد از درک کامل آن خواهید دید که چقدر مفید است.

    یادآوری از بخش اول:

    • Presenter: رابطی است میان View و Model. دیتا را از مدل دریافت میکند، آنرا ویرایش/آماده میکند و در اختیار View قرار میدهد. اما برخلاف MVC، تصمیم میگیرد که چه واکنشی به درخواست کاربر در ارتباط با View انجام دهد (مثلا زمانیکه دکمه ای کلیک شد).
    • View: عموما توسط اکتیوتی اجرا میشود و رفرنسی از Presenter را در خود دارد. تنها کاری که View انجام میدهد این است که متدخاصی را – از Presenter – زمانیکه اتفاقی در واسط کاربری افتاد (مثلا زمانیکه دکمه ای کلیک شد)، صدا کند.
    • Model: در برنامه ای که خیلی خوب لایه های معماری رعایت شده باشد، Model تنها چیزی است که منطق بیزینسی پروژه را اجرا میکند. اینطوری به آن نگاه کنید که دیتای مورد نیاز برای نماش داده شدن در View را دراختیارمان قرار میدهد.
    هدف نهایی از معماری مبتنی بر MVP افزایش هرچه بیشتر جداسازی لایه های پروژه است (separation of concerns). بنابراین باید از جدا بودن (isolation) لایه های Model, View و Controller مطمین باشیم. بنابراین براساس تعریف MVP، لایه های View و Model نباید به هیچ طریقی باهم ارتباط برقرار کنند. بنابراین تمام ارتباطات باید از طریق لایه Presentation برقرار شود.
    نمودار عمل (Action Diagram) بین لایه ها
    خب، بیایید یک برنامه بسیار ساده را درنظر بگیریم که به کاربر اجازه میدهد تا نوشته ای را منتشر کند. کاربر متنی را وارد میکند، برنامه هم آنرا ذخیره میکند و سپس نشان میدهد. با فرض اینکه برنامه براساس معماری MVP نوشته شده است، مراحل انجام کار بصورت نمودار زیر خواهد بود.



    MVP_ActionDiagram.png
    Model View Presenter (MVP) action diagram


    چیزی که نمودار میگوید این است که:

    1. کاربر بر روی دکمه “insert note” کلیک میکند. View متن کاربر را به Presenter ارسال میکند.
    2. Presenter، یک آبجکت جدید از Note با استفاده از متن داده شده می سازد. سپس Model را فرامیخواند و آبجکت ساخته شده را به متد مدل تحویل میدهد تا مدل آنرا در دیتابیس ذخیره کند.
    3. Model، آبجکت را در دیتابیس ذخیره میکند و با استفاده از CallBack، با/بدون موفقیت انجام شدن ترنزکشن را به اطلاع Presenter میرساند.
    4. Presenter، نتیجه را دریافت میکند و براساس آن به View میگوید که چه چیزی را به کاربر نشان دهد.
    نمودار بالا اطلاعات خیلی خوبی به ما میدهد که قرار است در کد چطور عمل کنیم. طریقه برقرار کردن ارتباط بین لایه ها به روش های مختلف انجام میشود. یک روش انتقال مستقیم داده به آبجکت است (direct object access)، یک روش استفاده از اینترفیس است، روش دیگر استفاده از یک نوع EventBus است. در این مطلب برای افزایش جداسازی (isolation) از روش دوم که استفاده از اینترفیس است، استفاده میکنیم. همچنین استفاده از اینترفیس روش توصیه شده از طرف اندروید است.

    نمودار کلاس (Class Diagram) بین لایه ها
    حالا بیایید با استفاده از نمودار عمل بالا به ساخت نمودار کلاس بپردازیم. فقط تغییر کوچکی در نمودار بالا میدهیم و بجای استفاده از CallBack برای ارسال نتیجه از Presenter به Model، از اینترفیس استفاده میکنیم. استفاده از اینترفیس کیفیمت کار بالاتر میبرد اگرچه بعضی ها ممکن است ادعا کنند، استفاده از CallBack میزان جداسازی (isolation of concerns) را بالاتر میبرد.



    MVP_ClassDiagram.png
    Model View Presenter Class Diagram


    1. Presenter، متدهای تعریف شده در اینترفیس PresenterOps را اجرا (implement) میکند.
    2. View رفرنسی از PresenterOps میگیرد تا به Presenter دسترسی پیدا کند.
    3. Model، متدهای ModelOps را اجرا(implement) میکند.
    4. Presenter رفرنسی از ModelOps میگیرد تا به Model دسترسی پیدا کند.
    5. Presenter، متدهای RequiredPresenterOps را اجرا (implement) میکند.
    6. Model رفرنسی از RequiredPresenterOps میگیرد تا به Presenter دسترسی پیدا کند.
    7. View، متدهای RequiredViewOps را اجرا (implement) میکند.
    8. Presenter رفرنسی از RequiredViewOps میگیرد تا به View دسترسی پیدا کند.
    اجرا کردن MVP در برنامه اندروید
    خیلی تا حالا داستان! تعریف کردیم، خب، یک راست برویم سراغ کد. برای بهتر سازمان دادن بکار از اینترفیس ها را داخل کلاسها قرار داده ام تا فهم کار آسانتر شود. از آنجاییکه اجرای MVP باندازه کافی پیچیدگی دارد، هیچ متدی را خارج از MVP تعریف نمیکنیم. فرض را بر این میگذارم که شما بخوبی با AndroidSDK آشنا هستید.



    Please, ورود or عضویت to view URLs content!

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    /*
    * Aggregates all communication operations between MVP pattern layer:
    * Model, View and Presenter
    */
    public interface MainMVP {

    /**
    * View mandatory methods. Available to Presenter
    * Presenter -> View
    */
    interface RequiredViewOps {
    void showToast(String msg);
    void showAlert(String msg);
    // any other ops
    }

    /**
    * Operations offered from Presenter to View
    * View -> Presenter
    */
    interface PresenterOps{
    void onConfigurationChanged(RequiredViewOps view);
    void onDestroy(boolean isChangingConfig);
    void novaNota(String textoNota);
    void deletaNota(Nota nota);
    // any other ops to be called from View
    }

    /**
    * Operations offered from Presenter to Model
    * Model -> Presenter
    */
    interface RequiredPresenterOps {
    void onNotaInserida(Nota novaNota);
    void onNotaRemovida(Nota notaRemovida);
    void onError(String errorMsg);
    // Any other returning operation Model -> Presenter
    }

    /**
    * Model operations offered to Presenter
    * Presenter -> Model
    */
    interface ModelOps {
    void insereNota(Nota nota);
    void removeNota(Nota nota);
    void onDestroy();
    // Any other data operation
    }
    }


    Please, ورود or عضویت to view URLs content!

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    public class MainPresenter
    implements MainMVP.RequiredPresenterOps, MainMVP.PresenterOps {

    // Layer View reference
    private WeakReference mView;
    // Layer Model reference
    private MainMVP.ModelOps mModel;

    // Configuration change state
    private boolean mIsChangingConfig;

    public MainPresenter(MainMVP.RequiredViewOps mView) {
    this.mView = new WeakReference<>(mView);
    this.mModel = new MainModel(this);
    }

    /**
    * Sent from Activity after a configuration changes
    * @param view View reference
    */
    @Override
    public void onConfigurationChanged(MainMVP.RequiredViewOps view) {
    mView = new WeakReference<>(view);
    }

    /**
    * Receives {@link MainActivity#onDestroy()} event
    * @param isChangingConfig Config change state
    */
    @Override
    public void onDestroy(boolean isChangingConfig) {
    mView = null;
    mIsChangingConfig = isChangingConfig;
    if ( !isChangingConfig ) {
    mModel.onDestroy();
    }
    }

    /**
    * Called by user interaction from {@link MainActivity}
    * creates a new Note
    */
    @Override
    public void newNote(String noteText) {
    Note note = new Note();
    note.setText(textoNota);
    note.setDate(getDate());
    mModel.insertNote(note);
    }

    /**
    * Called from {@link MainActivity},
    * Removes a Note
    */
    @Override
    public void removeNote(Note note) {
    mModel.removeNote(note);
    }

    /**
    * Called from {@link MainModel}
    * when a Note is inserted successfully
    */
    @Override
    public void onNoteInsert(Note newNote) {
    mView.get().showToast("New register added at " + newNote.getDate());
    }

    /**
    * Receives call from {@link MainModel}
    * when Note is removed
    */
    @Override
    public void onNoteRemoved(Note noteRemoved) {
    mView.get().showToast("Note removed);
    }

    /**
    * receive errors
    */
    @Override
    public void onError(String errorMsg) {
    mView.get().showAlert(errorMsg);
    }
    }


    Please, ورود or عضویت to view URLs content!

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    public class MainModel
    implements MainMVP.ModelOps {

    // Presenter reference
    private MainMVP.RequiredPresenterOps mPresenter;

    public MainModel(MainMVP.RequiredPresenterOps mPresenter) {
    this.mPresenter = mPresenter;
    }

    /**
    * Sent from {@link MainPresenter#onDestroy(boolean)}
    * Should stop/kill operations that could be running
    * and aren't needed anymore
    */
    @Override
    public void onDestroy() {
    // destroying actions
    }

    // Insert Note in DB
    @Override
    public void insertNote(Note note) {
    // data business logic
    // ...
    mPresenter.onNoteInserted(note);
    }

    // Removes Note from DB
    @Override
    public void removeNote(Note note) {
    // data business logic
    // ...
    mPresenter.onNoteRemoved(note);
    }
    }
    اتصال کلاس ها به اندروید
    در MVP، لایه View مسوول ساخت لایه Presenter است که این لایه هم به نوبه خود مسوول ساخت آبجکت View است. با توجه به اینکه در اندروید اکتیویتی نقش View را بازی میکند، باید خصوصیات اکتیویتی و بطور خاص
    Please, ورود or عضویت to view URLs content!
    آنرا مد نظر قرار دهیم (اینکه اکتیویتی در هر زمان ممکن است ساخته/نابود شود که به طریق اولی آبجکت های داخلش نیز متاثر خواهند شد). بنابراین احتیاج به المان چهارمی داریم، StateMaintainer، که مسوولیت کنترل وضعیت Presenter و View در طی تغییر چرخه حیات اکتیویتی را دارا باشد. نمودار ساده MVP که متاثر از چرخه حیات اکتیویتی است را در زیر مشاهده میکنید.



    MVP_Activity_Lifecycle.png
    MVP Objects destruction and reconstruction during Activity lifecycle changes


    1. اکتیویتی، آبجکتی از Presenter میسازد و PresenterOps را ذخیره میکند. Presenter هم در StateMaintainer ذخیره میشود.
    2. Presenter، در طی ساخت خود، RequiredViewOps را دریافت میکند و آبجکت Model را میسازد.
    3. مدل، RequiredPresenterOps را دریافت میکند.
    4. وقتی اکتیویتی درحال نابود شدن است، Presenter را آگاه میکند.
    5. Presenter، اطلاعات را پردازش میکند و Model را آگاه میکند و اطلاعات مورد نیاز مدل را به آن میدهد.
    6. اکتیویتی، Presenter را از StateMaintainer بازیابی میکند، RequiredViewOps را ارسال میکند و آنرا از وضعیتش آگاه میکند.
    کلاس StateMaintainer
    این اجرای کلاس StateMaintainer میتواند برای ذخیره وضعیت هر آبجکتی استفاده شود.

    Please, ورود or عضویت to view URLs content!

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    public class StateMaintainer {
    protected final String TAG = getClass().getSimpleName();

    private final String mStateMaintenerTag;
    private final WeakReference mFragmentManager;
    private StateMngFragment mStateMaintainerFrag;

    /**
    * Constructor
    * @param fragmentManager FragmentManager reference
    * @param stateMaintainerTAG the TAG used to insert the state maintainer fragment
    */
    public StateMaintainer(FragmentManager fragmentManager, String stateMaintainerTAG) {
    mFragmentManager = new WeakReference<>(fragmentManager);
    mStateMaintenerTag = stateMaintainerTAG;
    }

    /**
    * Create the state maintainer fragment
    * @return true: the frag was created for the first time
    * false: recovering the object
    */
    public boolean firstTimeIn() {
    try {
    // Recovering the reference
    mStateMaintainerFrag = (StateMngFragment)
    mFragmentManager.get().findFragmentByTag(mStateMaintenerTag);

    // Creating a new RetainedFragment
    if (mStateMaintainerFrag == null) {
    Log.d(TAG, "Creating a new RetainedFragment " + mStateMaintenerTag);
    mStateMaintainerFrag = new StateMngFragment();
    mFragmentManager.get().beginTransaction()
    .add(mStateMaintainerFrag, mStateMaintenerTag).commit();
    return true;
    } else {
    Log.d(TAG, "Returns a existent retained fragment existente " + mStateMaintenerTag);
    return false;
    }
    } catch (NullPointerException e) {
    Log.w(TAG, "Error firstTimeIn()");
    return false;
    }
    }


    /**
    * Insert Object to be preserved during configuration change
    * @param key Object's TAG reference
    * @param obj Object to maintain
    */
    public void put(String key, Object obj) {
    mStateMaintainerFrag.put(key, obj);
    }

    /**
    * Insert Object to be preserved during configuration change
    * Uses the Object's class name as a TAG reference
    * Should only be used one time by type class
    * @param obj Object to maintain
    */
    public void put(Object obj) {
    put(obj.getClass().getName(), obj);
    }


    /**
    * Recovers saved object
    * @param key TAG reference
    * @param Class type
    * @return Objects
    */
    @SuppressWarnings("unchecked")
    public T get(String key) {
    return mStateMaintainerFrag.get(key);

    }

    /**
    * Verify the object existence
    * @param key Obj TAG
    */
    public boolean hasKey(String key) {
    return mStateMaintainerFrag.get(key) != null;
    }


    /**
    * Save and manages objects that show be preserved
    * during configuration changes.
    */
    public static class StateMngFragment extends Fragment {
    private HashMap<String, Object> mData = new HashMap<>();

    @Override
    public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    // Grants that the frag will be preserved
    setRetainInstance(true);
    }

    /**
    * Insert objects
    * @param key reference TAG
    * @param obj Object to save
    */
    public void put(String key, Object obj) {
    mData.put(key, obj);
    }

    /**
    * Insert obj using class name as TAG
    * @param object obj to save
    */
    public void put(Object object) {
    put(object.getClass().getName(), object);
    }

    /**
    * Recover obj
    * @param key reference TAG
    * @param Class
    * @return Obj saved
    */
    @SuppressWarnings("unchecked")
    public T get(String key) {
    return (T) mData.get(key);
    }
    }
    }
    اکتیویتی اصلی (لایه View)


    Please, ورود or عضویت to view URLs content!

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    public class MainActivity extends AppCompatActivity
    implements MainMVP.RequiredViewOps {

    protected final String TAG = getClass().getSimpleName();

    // Responsible to maintain the Objects state
    // during changing configuration
    private final StateMaintainer mStateMaintainer =
    new StateMaintainer( this.getFragmentManager(), TAG );

    // Presenter operations
    private MainMVP.PresenterOps mPresenter;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    startMVPOps();
    setContentView(R.layout.activity_main);
    Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
    setSupportActionBar(toolbar);
    FloatingActionButton fab = (FloatingActionButton) findViewById(R.id.fab);
    }


    /**
    * Initialize and restart the Presenter.
    * This method should be called after {@link Activity#onCreate(Bundle)}
    */
    public void startMVPOps() {
    try {
    if ( mStateMaintainer.firstTimeIn() ) {
    Log.d(TAG, "onCreate() called for the first time");
    initialize(this);
    } else {
    Log.d(TAG, "onCreate() called more than once");
    reinitialize(this);
    }
    } catch ( InstantiationException | IllegalAccessException e ) {
    Log.d(TAG, "onCreate() " + e );
    throw new RuntimeException( e );
    }
    }


    /**
    * Initialize relevant MVP Objects.
    * Creates a Presenter instance, saves the presenter in {@link StateMaintainer}
    */
    private void initialize( MainMVP.RequiredViewOps view )
    throws InstantiationException, IllegalAccessException{
    mPresenter = new MainPresenter(view);
    mStateMaintainer.put(MainMVP.PresenterOps.class.getSimpleName(), mPresenter);
    }

    /**
    * Recovers Presenter and informs Presenter that occurred a config change.
    * If Presenter has been lost, recreates a instance
    */
    private void reinitialize( MainMVP.RequiredViewOps view)
    throws InstantiationException, IllegalAccessException {
    mPresenter = mStateMaintainer.get( MainMVP.PresenterOps.class.getSimpleName() );

    if ( mPresenter == null ) {
    Log.w(TAG, "recreating Presenter");
    initialize( view );
    } else {
    mPresenter.onConfigurationChanged( view );
    }
    }


    // Show AlertDialog
    @Override
    public void showAlert(String msg) {
    // show alert Box
    }

    // Show Toast
    @Override
    public void showToast(String msg) {
    Toast.makeText(getApplicationContext(), msg, Toast.LENGTH_SHORT).show;
    }
    }
    سخن آخر
    این مطلب خیلی طولانی شد، شرمنده! اما واقعا امیدوارم بدرد کسی بخورد. در قسمت آخر (بعدی)، طریقه استفاده از final framework را شرح میدهم که باعث ساده تر شده کار در استفاده از الگوی MVP میشود. همچنین به چند گره ای که باعث گیر کردن کار در استفاده از MVP میشود، اشاره خواهم کرد
     

    سیده آمین ارمان

    کاربر نگاه دانلود
    کاربر نگاه دانلود
    عضویت
    2016/05/10
    ارسالی ها
    1,730
    امتیاز واکنش
    20,744
    امتیاز
    795
    محل سکونت
    البرز
    این مطلب آخرین قسمت از سری مطالب معماری برنامه اندروید براساس MVP است. موارد زیر مورد بررسی قرار خواهند گرفت:

    چگونه با استفاده از کتابخانه simple-mvp سرعت برنامه نویسی و اجرای کار بر مبنای MVP را بالاتر ببریم.
    مشکل های متداول در برنامه نویسی مبتنی بر MVP
    اگر مطالب قبلی را نخوانده اید یا میخواهید با مفاهیم Model-View-Controller آشنا شوید، مطالب قبلی را ببینید:

    معماری برنامه اندروید براساس MVP (بخش اول)
    معماری برنامه اندروید براساس MVP (بخش دوم)

    استفاده از کتابخانه Simple-MVP

    این کتابخانه بر اساس مفاهیمی که دکتر داگلاس اشمیت معرفی کرده است، ساخته شده است. از آنجاییکه کتابخانه اصول استاندارد را رعایت میکند (canonical form principles)، بنابراین از هیچ کتابخانه و یا منبعی خارج از Android SDK استفاده نمیکند. اما برای اینکه این چهارچوب/چارچوب (framework) درست کار بکند، باید اصول استفاده از آن رعایت شود.

    هشدار: این کتابخانه هنوز تحت آزمایش است و برای استفاده های تحقیقاتی منتشر شده است. به منظور ارتقای کتابخانه، نام بعضی از متدها و یا عملکردهای این کتابخانه در آینده ممکن است عوض شود.

    simple-mvp repository
    simple-mvp JavaDoc
    راه اندازی سریع کتابخانه

    ۱- ابتدا برای دانلود کتابخانه، آدرس آنرا به app/build.gradle میدهیم.

    ?
    1
    compile 'com.tinmegali.mvp:mvp:0.0.7'
    ۲- برای ارتباط برقرار کردن بین لایه های مختلف MVP، به چهار اینترفیس زیر نیاز است.

    ?
    1
    2
    3
    4
    interface RequiredViewOps extends ActivityView
    interface ProvidedPresenterOps extends PresenterOps
    interface RequiredPresenterOps
    interface ProvidedModelOps extends ModelOps
    برای دیدن نمونه کد اینجا را کلیک کنید.

    ۳- اجرا کردن (Implement) آبجکت های MVP که مدل نوعی شان (Generic) را توسعه میدهند (Extend). [بعضی وقتها ترجمه جمله ای بسیار پیچیده میشود. اگر ترجمه این خط را متوجه نشدید، متن انگلیسی آن این است:Implement MVP objects extending its generics]. برای آسان سازی کار، هر لایه یک آبجکت مخصوص بخود دارد (generic). این کلاسها (که آبجکت ها را از آنها ساخته ایم) اکثر کارهای مربوط به پیکربندی MVP را برای ما انجام میدهند. تنها کاری که باید بکنیم این است که این آبجکتها را راه اندازی (initialize) کنیم و کار تمام است.

    ?
    1
    2
    3
    class MODEL extends GenericModel implements MVP_MainActivity.ProvidedModelOps
    class VIEW_Activity extends GenericMVPActivity implements MVP_MainActivity.RequiredViewOps
    class MainPresenter extends GenericPresenter implements MVP_MainActivity.RequiredPresenterOps, MVP_MainActivity.ProvidedPresenterOps
    MODEL

    ?
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    public class MainModel extends GenericModel
    implements MVP_MainActivity.ProvidedModelOps {

    /**
    * Method that recovers a reference to the PRESENTER
    * - You must ALWAYS call {@link super#onCreate(Object)} here
    * @param presenterOps Presenter interface
    */
    @Override
    public void onCreate(MVP_MainActivity.RequiredPresenterOps presenterOps) {
    super.onCreate(presenterOps);
    // initialize objects
    }

    /**
    * Called by layer PRESENTER when VIEW pass for a reconstruction/destruction.
    * Usefull for kill/stop activities that could be running on the background
    * Threads
    * @param isChangingConfiguration Informs that a change is occurring on configuration
    */
    @Override
    public void onDestroy(boolean isChangingConfiguration) {
    // kill or stop actions
    }
    }
    PRESENTER

    ?
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    public class MainPresenter
    extends GenericPresenter<MVP_MainActivity.RequiredPresenterOps, MVP_MainActivity.ProvidedModelOps, MVP_MainActivity.RequiredViewOps, MainModel>
    implements
    MVP_MainActivity.RequiredPresenterOps,
    MVP_MainActivity.ProvidedPresenterOps
    {

    /**
    * Operation called during VIEW creation in
    * {@link com.tinmegali.mvp.mvp.GenericMVPActivity#onCreate(Class, Object)}
    * Responsible to initialize MODEL.
    * @param view The current VIEW instance
    */
    @Override
    public void onCreate(MVP_MainActivity.RequiredViewOps view) {
    super.onCreate(MainModel.class, this);
    // super.onCreate(, );
    setView( view );
    }

    /**
    * Operation called by VIEW after its reconstruction.
    * @param view The current VIEW instance
    */
    @Override
    public void onConfigurationChange(MVP_MainActivity.RequiredViewOps view) {
    setView(view);
    }
    }
    VIEW

    ?
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    /*
    * VIEW layer of MVP pattern *
    * Layer VIEW no padrão Model View Presenter (MVP)
    */
    public class MainActivity
    extends
    GenericMVPActivity<MVP_MainActivity.RequiredViewOps, MVP_MainActivity.ProvidedPresenterOps, MainPresenter>
    implements
    MVP_MainActivity.RequiredViewOps{

    /**
    * Method that initialized MVP objects
    * {@link super#onCreate(Class, Object)} should always be called
    */
    @Override
    protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    // super método obrigatório
    super.onCreate(MainPresenter.class,this);
    setContentView(R.layout.activity_main);
    Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
    setSupportActionBar(toolbar);

    FloatingActionButton fab = (FloatingActionButton) findViewById(R.id.fab);
    fab.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View view) {
    Snackbar.make(view, "Replace with your own action", Snackbar.LENGTH_LONG)
    .setAction("Action", null).show();
    }
    });
    }

    // View operations
    }
    نمونه کد مربوط به کلاس Model

    نمونه کد مربوط به کلاس Presenter

    نمونه کد مربوط به کلاس View

    اجتناب از بوجود آمدن اشکال ها

    هرگاه خواستید از ()getView استفاده کنید، فراموش نکنید که null بودن آن را چک کنید. از آنجاییکه وجود VIEW کاملا به وجود اکتیویتی وابسته است، ممکن است اکتیویتی نابود شده باشد (Destroyed).
    اگر منطق اکتیویتی تان پیچیده است، خست به خرج ندهید! و از چندیدن PRESENTER استفاده کنید.
    اگر از چندیدن PRESENTER استفاده میکنید، راحت ترین کار برای ارسال دیتا بین آنها استفاده از EventBus است (انشالله در أینده در مطلبی طرز استفاده از آنرا شرح خواهم داد).
    هرگاه در لایه VIEW متد (onCreate(Bundle savedInstanceState را override کردید، ;(<super.onCreate(<Presenter.class>, <RequiredViewOps را هم داخلش قرار دهید.
    هرگاه در لایه PRESENTER متد (onCreate(MVP_MainActivity.RequiredViewOps view را override کردید، فراموش نکنید که ;(<super.onCreate(<Model.class>, <RequiredPresenterOps و ;(setView(view را هم اجرا کنید.
    همیشه در لایه PRESENTER، زمانیکه پیکربندی تغییر کرد (مثلا اگر برنامه شما از هردو حالت چرخش دیوایس پشتیبانی میکند)، VIEW را بازتعریف (redefine) کنید.
    ?
    1
    2
    3
    4
    @Override
    public void onConfigurationChange(MVP_MainActivity.RequiredViewOps view) {
    setView(view);
    }
    تست برنامه با استفاده از Unit Testing

    شاید قسمت یکم پیچیده داستان تست برنامه و علی الخصوص تست Presenter باشد. در اینجا به تست MVP میپردازم.

    تست Presenter

    باید قادر باشید بدون هیچ مشکلی Presenter را تست کنید. سعی نکنید onCreate متد را صدا بزنید. وظیفه این متد ساخت Model و View است. بنابراین زمان تست نیازی نیست که این متد فراخوانی شود (اگر هدف از تست صرف Presenter است). خیلی ساده Presenter را مانند کد زیر بسازید و کار تمام است.

    ?
    1
    mPresenter = new MainPresenter();
    از آنجاییکه Presenter نقش واسط را بازی میکند، تست تان باید View و Model را ماک (Mock) کند. دراین حالت میتوانید از متدهای setView. و testWithModel. استفاده کنید. اینها متدهای کمک کننده (helper methods) هستند که منحصرا به منظور ماک کردن Model در Presenter استفاده میشنوند.

    ماک کردن Model در Presenter
    ?
    1
    2
    mModel = mock(MainModel.class);
    mPresenter.testWithModel(mModel);
    ماک کردن View در Presenter
    ?
    1
    2
    mView = mock(MainActivity.class);
    mPresenter.setView(mView);
    تست Model

    تست این لایه پیچیدگی خاصی ندارد. اگر نیاز دارید تا Presenter ماک شده را به آن بدهید میتوانید بطریق زیر عمل کنید. onCreate را صدا بزنید و ماک Presenter را به آن بدهید.

    ?
    1
    2
    3
    mModel = new MainModel();
    mPresenter = mock(MainPresenter.class);
    mModel.onCreate(mPresenter);
     

    برخی موضوعات مشابه

    بالا