معماری MVVM در اندروید چیست؟

معماری MVVM یک معماری Model-View-ViewModel است که اتصال فشرده بین هر کامپوننت را از بین می برد. از همه مهم تر، در این معماری، فرزندان ارجاع مستقیم به پدران ندارند ( فرزند و پدر در شی گرایی ) و از طریق مشاهدات خود مرجع را تعیین می کنند.

MVVM  اجازه می دهد تا منطق رابط کاربر را از منطق تجاری جدا کنید. هدف  mvvm همراه با هدف سایر الگوهای MVC دستیابی به اصل زیر است:

“ساده نگه داشتن کد UI و آزاد از منطق برنامه، برای مدیریت آسان تر”.

لایه های MVVM

مدل یا model : مدل، داده ها و منطق تجاری برنامه اندروید را نشان می دهد. یکی از راهکارهای پیاده سازی این لایه ، افشای داده های آن از طریق مشاهدات است تا از ViewModel  یا هر مشاهده گر یا مصرف کننده دیگر جدا شود.

 نما یا view :  شامل کد های UI ، Fragment , Activity)  XML , ( می باشد. این لایه عملکرد کاربر را به ViewModel ارسال می کند اما پاسخ را مستقیماً دریافت نمی کند. برای دریافت پاسخ ، باید شامل مشاهداتی باشد که ViewModel نیز در معرض آن است.

نقش view در این الگو اشتراک در ViewModel قابل مشاهده، برای بدست آوردن داده ها جهت به روز کردن عناصر UI است.

 ویو مدل یا ViewModel : پلی است بین View و Model (منطق تجارت). هیچ نشانی ندارد که View مجبور باشد از آن استفاده کند زیرا اشاره مستقیمی به View ندارد. یکی از استراتژی های مهم پیاده سازی این لایه جدا کردن آن از View است ، به عنوان مثال ،  ViewModel نباید از نمایی که با آن تعامل دارد آگاه باشد.

ویومدل با مدل تعامل دارد و قابل مشاهده است و می تواند توسط View مشاهده شود.

نمودار زیر اجزای MVVM و تعاملات اصلی بین آن ها را نشان می دهد.

لایه های MVVM

 

LiveData چیست

LiveData یکی از کامپوننت های معماری تازه معرفی شده است. LiveData یک نگهدارنده برای داده های قابل مشاهده می باشد. با این کار کامپوننت های موجود در برنامه شما قادر به مشاهده اشیا LiveData برای تغییرات هستند و البته بدون ایجاد مسیرهای وابستگی سخت بین آن ها. این قابلیت، به طور کامل تولید کننده شی LiveData را از مصرف کننده شی  LiveData جدا می کند.

علاوه بر این ، یک مزیت بزرگ در LiveData وجود دارد . LiveData  به وضعیت چرخه کامپوننت های اپلیکیشن شما (فعالیت ها ، قطعات ، خدمات) احترام می گذارد و مدیریت چرخه حیات شی را مدیریت می کند.

انواع معماری در اندروید

در حال حاضر سه روش عمده برای طراحی برنامه ها و اپلیکیشن های اندروید وجود دارد:

 اندروید استاندارد (Model-View-Controller) یا mvc : این رویکرد “پیش فرض” با فایل های layout , activity  Fragment, به عنوان کنترل کننده و مدل های مورد استفاده برای داده و ماندگاری است.

با استفاده از این روش ، activity ها، پردازش داده ها و به روزرسانی بازدیدها را بر عهده دارند. Activity ها مانند یک کنترل کننده در MVC عمل می کنند اما دارای برخی مسئولیت های اضافی هستند که باید بخشی از این دیدگاه باشد. مشکلی که در این معماری استاندارد وجود دارد این است که activity و fragment ها می توانند بسیار بزرگ شوند و آزمایش آنها بسیار دشوار خواهد بود.

 معماری clean (Model View Presenter) یا mvp : هنگام استفاده از MVP ، activity ها و Fragment ها به بخشی از لایه View تبدیل می شوند و بیشتر کارها را به اشیا ارائه می دهند. هر فعالیت دارای یک ارائه دهنده منطبق است که کلیه دسترسی به مدل را کنترل می کند. مجریان همچنین هنگام آماده شدن داده ها برای نمایش ، فعالیت ها را مطلع می کنند.

 Data-binding MVVM (Model-View-ViewModel) – ViewModels: هنگام درخواست از ویو از طریق چارچوب اتصال داده اندروید ، داده ها را از مدل بازیابی می کند. مزایای MVVM این است که ، activity و fragment بسیار سبک می شوند. علاوه بر این ، نوشتن تست های واحد آسان تر می شود زیرا ویومدل از ویو جدا می شود.

 

مقایسه بین MVC / MVP / MVVM

برخلاف MVP و MVVM ، activity ها در اندروید وابستگی زیادی به اندروید دارند و از این رو آزمایش واحد در MVP / MVVM آسان است.

با این حال ، XML در MVVM برای اهداف اتصال داده پیچیده تر می شود.

تفاوت mvvm با mvc   و همچنین تفاوت mvvm با mvp در جدول زیر مشخص شده است.

 

روش های پیاده سازی MVVM در Android

دو روش برای پیاده سازی mvvm در اندروید وجود دارد:

  •  data binding

  •   RX.java

در اینجا از دیتا بایندینگ استفاده می کنیم. اگر بخواهید از livedata یا data binding استفاده نکنید، باید از rxjava استفاده کنید.

پیاده سازی معماری MVVM در اندروید

وقتی می خواهیم از MVVM در اندروید استفاده کنیم ، از این معماری استفاده می کنیم.

معماری MVVM در اندروید

انجام پروژه عملی

حال می خواهیم یک پروژه جدید با کوتلین و سایر وابستگی های مورد نیاز اش راه اندازی کنیم.

۱) یک پروژه جدید Android Studio را شروع و باز کنید.

۲) گزینه new و گزینه Next را انتخاب کنید.

۳) نام پروژه:MVVM-Architecture-Android

۴) نام پکیج: com.mindorks.framework.mvvm

۵) زبان: کوتلین

۶) اتمام

پروژه شروع شما اکنون آماده است.

۷) وابستگی های زیر را در سطح برنامه build.gradle اضافه کنید.

implementation "androidx.recyclerview:recyclerview:1.1.0"

implementation 'android.arch.lifecycle:extensions:1.1.1'

implementation 'com.github.bumptech.glide:glide:4.9.0'

implementation 'com.amitshekhar.android:rx2-android-networking:1.0.2'

implementation 'io.reactivex.rxjava2:rxjava:2.2.18'

implementation 'io.reactivex.rxjava2:rxandroid:2.1.1'

اکنون پروژه با وابستگی هایش آماده است.

ساختار پروژه

برای این پروژه ، ما می خواهیم نسخه مبتدی MVVM را دنبال کنیم. بسته ما در پروژه به شرح زیر خواهد بود:

۸) بسته utils را تنظیم کنید.

package com.mindorks.framework.mvvm.utils



enum class Status {

SUCCESS,

ERROR,

LOADING

}

 

حال به یک کلاس نرم افزاری نیاز داریم که مسئول برقراری ارتباط وضعیت تماس شبکه با لایه   UI باشد که از آن به عنوان Resource نام می بریم.

۹) بنابراین ، یک کلاس داده Kotlin در داخل همان بسته utils ایجاد کنید و کد زیر را اضافه کنید.

package com.mindorks.framework.mvvm.utils



data class Resource<out T>(val status: Status, val data: T?, val message: String?) {



companion object {



fun <T> success(data: T?): Resource<T> {

return Resource(Status.SUCCESS, data, null)

}



fun <T> error(msg: String, data: T?): Resource<T> {

return Resource(Status.ERROR, data, msg)

}



fun <T> loading(data: T?): Resource<T> {

return Resource(Status.LOADING, data, null)

}



}



}

 

۱۰) اکنون ، در این بخش لایه داده را تنظیم می کنیم:

۱- بسته داده را ایجاد کنید.

۲- داخل داده ها را مدل کنید.

۳- پاسخ API JSON به این شکل خواهد بود.

[

{

"id": "1",

"name": "Mrs. Nedra Gerhold",

"avatar": "https://s3.amazonaws.com/uifaces/faces/twitter/to_soham/128.jpg",

"email": "Lonzo6@hotmail.com"

},

{

"id": "2",

"name": "Spencer McKenzie",

"avatar": "https://s3.amazonaws.com/uifaces/faces/twitter/irae/128.jpg",

"email": "Josiah.Hane@gmail.com"

}

]

 

۱۱) بر این اساس ، باید کلاس داده خود را ایجاد کنیم.

اکنون ، یک فایل کاربر کوتلین در داخل بسته مدل ایجاد کنید. کلاس داده user:

package com.mindorks.framework.mvvm.data.model



import com.google.gson.annotations.SerializedName



data class User(

@SerializedName("id")

val id: Int = 0,

@SerializedName("name")

val name: String = "",

@SerializedName("email")

val email: String = "",

@SerializedName("avatar")

val avatar: String = ""

)

 

۱۲) اکنون ، باید لایه شبکه خود را تنظیم کنیم.

۱- بسته api  را در داخل داده ها ایجاد کنید .

۲- سپس ، یک رابط کاربری ApiService در داخل بسته api ایجاد کرده و کد زیر را اضافه کنید.

package com.mindorks.framework.mvvm.data.api



import com.mindorks.framework.mvvm.data.model.User

import io.reactivex.Single



interface ApiService {



fun getUsers(): Single<List<User>>



}

 

۱۳) سپس ، یک کلاس ApiServiceImpl ایجاد کنید که رابط ApiService را در داخل بسته api پیاده سازی می کند و کد زیر را اضافه کنید.

package com.mindorks.framework.mvvm.data.api



import com.mindorks.framework.mvvm.data.model.User

import com.rx2androidnetworking.Rx2AndroidNetworking

import io.reactivex.Single



class ApiServiceImpl : ApiService {



override fun getUsers(): Single<List<User>> {

return Rx2AndroidNetworking.get("https://5e510330f2c0d300147c034c.mockapi.io/users")

.build()

.getObjectListSingle(User::class.java)

}



}

۱۴) اکنون یک کلاس ApiHelper در داخل بسته api ایجاد کرده و کد زیر را اضافه کنید.

package com.mindorks.framework.mvvm.data.api



import com.mindorks.framework.mvvm.data.model.User

import com.rx2androidnetworking.Rx2AndroidNetworking

import io.reactivex.Single



class ApiServiceImpl : ApiService {



override fun getUsers(): Single<List<User>> {

return Rx2AndroidNetworking.get("https://5e510330f2c0d300147c034c.mockapi.io/users")

.build()

.getObjectListSingle(User::class.java)

}



}

 

۱۵) بسته repository را در داخل داده ایجاد کنید.

اکنون ، یک کلاس MainRepository در داخل بسته مخزن ایجاد کرده و کد زیر را اضافه کنید.

package com.mindorks.framework.mvvm.data.repository



import com.mindorks.framework.mvvm.data.api.ApiHelper

import com.mindorks.framework.mvvm.data.model.User

import io.reactivex.Single



class MainRepository(private val apiHelper: ApiHelper) {



fun getUsers(): Single<List<User>> {

return apiHelper.getUsers()

}



}

 

اکنون ، در این بخش ، UI را تنظیم می کنیم ، پروژه را می سازیم و روی دستگاه اجرا می کنیم.

۱۶) ایجاد بسته ui

۱۷) ایجاد بسته  اصلی درون بسته ui

۱۸) ایجاد بسته  ویو داخل بسته اصلی

۱۹) MainActivity را به بسته view منتقل کنید

۲۰) بسته  viewmodel را داخل بسته اصلی ایجاد کنید.

۲۱) اکنون ، یک کلاس MainViewModel از کلاس Kotlin را در داخل همان بسته viewmodel ایجاد کرده و کد زیر را اضافه کنید. در اینجا از LiveData استفاده کرده ایم.

package com.mindorks.framework.mvvm.ui.main.viewmodel



import androidx.lifecycle.LiveData

import androidx.lifecycle.MutableLiveData

import androidx.lifecycle.ViewModel

import com.mindorks.framework.mvvm.data.model.User

import com.mindorks.framework.mvvm.data.repository.MainRepository

import com.mindorks.framework.mvvm.utils.Resource

import io.reactivex.android.schedulers.AndroidSchedulers

import io.reactivex.disposables.CompositeDisposable

import io.reactivex.schedulers.Schedulers



class MainViewModel(private val mainRepository: MainRepository) : ViewModel() {



private val users = MutableLiveData<Resource<List<User>>>()

private val compositeDisposable = CompositeDisposable()



init {

fetchUsers()

}



private fun fetchUsers() {

users.postValue(Resource.loading(null))

compositeDisposable.add(

mainRepository.getUsers()

.subscribeOn(Schedulers.io())

.observeOn(AndroidSchedulers.mainThread())

.subscribe({ userList ->

users.postValue(Resource.success(userList))

}, { throwable ->

users.postValue(Resource.error("Something Went Wrong", null))

})

)

}



override fun onCleared() {

super.onCleared()

compositeDisposable.dispose()

}



fun getUsers(): LiveData<Resource<List<User>>> {

return users

}



}

 

۲۲) بیایید طرح XML را تنظیم می کنیم.

در پوشه layout ، activity_main.xml را با کد زیر به روز کنید:

<?xml version="1.0" encoding="utf-8"?>

<androidx.constraintlayout.widget.ConstraintLayout 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=".ui.main.view.MainActivity">



<androidx.recyclerview.widget.RecyclerView

android:id="@+id/recyclerView"

android:layout_width="match_parent"

android:layout_height="match_parent"

android:visibility="gone" />



<ProgressBar

android:id="@+id/progressBar"

android:layout_width="wrap_content"

android:layout_height="wrap_content"

app:layout_constraintBottom_toBottomOf="parent"

app:layout_constraintEnd_toEndOf="parent"

app:layout_constraintStart_toStartOf="parent"

app:layout_constraintTop_toTopOf="parent" />



</androidx.constraintlayout.widget.ConstraintLayout>

 

۲۳) item_layout.xml را در پوشه layout اضافه کرده و کد زیر را اضافه کنید:

<?xml version="1.0" encoding="utf-8"?>

<androidx.constraintlayout.widget.ConstraintLayout 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:id="@+id/container"

android:layout_width="match_parent"

android:layout_height="60dp">



<ImageView

android:id="@+id/imageViewAvatar"

android:layout_width="60dp"

android:layout_height="0dp"

android:padding="4dp"

app:layout_constraintBottom_toBottomOf="parent"

app:layout_constraintStart_toStartOf="parent"

app:layout_constraintTop_toTopOf="parent" />



<androidx.appcompat.widget.AppCompatTextView

android:id="@+id/textViewUserName"

style="@style/TextAppearance.AppCompat.Large"

android:layout_width="0dp"

android:layout_height="wrap_content"

android:layout_marginStart="8dp"

android:layout_marginLeft="8dp"

android:layout_marginTop="4dp"

app:layout_constraintEnd_toEndOf="parent"

app:layout_constraintStart_toEndOf="@+id/imageViewAvatar"

app:layout_constraintTop_toTopOf="parent"

tools:text="MindOrks" />



<androidx.appcompat.widget.AppCompatTextView

android:id="@+id/textViewUserEmail"

android:layout_width="0dp"

android:layout_height="wrap_content"

app:layout_constraintEnd_toEndOf="parent"

app:layout_constraintStart_toStartOf="@+id/textViewUserName"

app:layout_constraintTop_toBottomOf="@+id/textViewUserName"

tools:text="MindOrks" />



</androidx.constraintlayout.widget.ConstraintLayout>

 

۲۴) بسته  آداپتور را داخل بسته اصلی ایجاد کنید.

اکنون ، یک کلاس MainAdapter از کلاس Kotlin در داخل همان بسته آداپتور ایجاد کرده و کد زیر را اضافه کنید.

package com.mindorks.framework.mvvm.ui.main.adapter



import android.view.LayoutInflater

import android.view.View

import android.view.ViewGroup

import androidx.recyclerview.widget.RecyclerView

import com.bumptech.glide.Glide

import com.mindorks.framework.mvvm.R

import com.mindorks.framework.mvvm.data.model.User

import kotlinx.android.synthetic.main.item_layout.view.*



class MainAdapter(

private val users: ArrayList<User>

) : RecyclerView.Adapter<MainAdapter.DataViewHolder>() {



class DataViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {

fun bind(user: User) {

itemView.textViewUserName.text = user.name

itemView.textViewUserEmail.text = user.email

Glide.with(itemView.imageViewAvatar.context)

.load(user.avatar)

.into(itemView.imageViewAvatar)

}

}



override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) =

DataViewHolder(

LayoutInflater.from(parent.context).inflate(

R.layout.item_layout, parent,

false

)

)



override fun getItemCount(): Int = users.size



override fun onBindViewHolder(holder: DataViewHolder, position: Int) =

holder.bind(users[position])



fun addData(list: List<User>) {

users.addAll(list)

}



}

 

۲۵) ایجاد بسته  پایه در داخل بسته ui

اکنون ، یک کلاس Kotlin ViewModelFactory در داخل بسته پایه ایجاد کرده و کد زیر را اضافه کنید.

package com.mindorks.framework.mvvm.ui.base



import androidx.lifecycle.ViewModel

import androidx.lifecycle.ViewModelProvider

import com.mindorks.framework.mvvm.data.api.ApiHelper

import com.mindorks.framework.mvvm.data.repository.MainRepository

import com.mindorks.framework.mvvm.ui.main.viewmodel.MainViewModel



class ViewModelFactory(private val apiHelper: ApiHelper) : ViewModelProvider.Factory {



override fun <T : ViewModel?> create(modelClass: Class<T>): T {

if (modelClass.isAssignableFrom(MainViewModel::class.java)) {

return MainViewModel(MainRepository(apiHelper)) as T

}

throw IllegalArgumentException("Unknown class name")

}



}

 

۲۶) اکنون ، باید کلاس MainActivity خود را تکمیل کنیم.

package com.mindorks.framework.mvvm.ui.main.view



import android.os.Bundle

import android.view.View

import android.widget.Toast

import androidx.appcompat.app.AppCompatActivity

import androidx.lifecycle.Observer

import androidx.lifecycle.ViewModelProviders

import androidx.recyclerview.widget.DividerItemDecoration

import androidx.recyclerview.widget.LinearLayoutManager

import com.mindorks.framework.mvvm.R

import com.mindorks.framework.mvvm.data.api.ApiHelper

import com.mindorks.framework.mvvm.data.api.ApiServiceImpl

import com.mindorks.framework.mvvm.data.model.User

import com.mindorks.framework.mvvm.ui.base.ViewModelFactory

import com.mindorks.framework.mvvm.ui.main.adapter.MainAdapter

import com.mindorks.framework.mvvm.ui.main.viewmodel.MainViewModel

import com.mindorks.framework.mvvm.utils.Status

import kotlinx.android.synthetic.main.activity_main.*



class MainActivity : AppCompatActivity() {



private lateinit var mainViewModel: MainViewModel

private lateinit var adapter: MainAdapter



override fun onCreate(savedInstanceState: Bundle?) {

super.onCreate(savedInstanceState)

setContentView(R.layout.activity_main)

setupUI()

setupViewModel()

setupObserver()

}



private fun setupUI() {

recyclerView.layoutManager = LinearLayoutManager(this)

adapter = MainAdapter(arrayListOf())

recyclerView.addItemDecoration(

DividerItemDecoration(

recyclerView.context,

(recyclerView.layoutManager as LinearLayoutManager).orientation

)

)

recyclerView.adapter = adapter

}



private fun setupObserver() {

mainViewModel.getUsers().observe(this, Observer {

when (it.status) {

Status.SUCCESS -> {

progressBar.visibility = View.GONE

it.data?.let { users -> renderList(users) }

recyclerView.visibility = View.VISIBLE

}

Status.LOADING -> {

progressBar.visibility = View.VISIBLE

recyclerView.visibility = View.GONE

}

Status.ERROR -> {

//Handle Error

progressBar.visibility = View.GONE

Toast.makeText(this, it.message, Toast.LENGTH_LONG).show()

}

}

})

}



private fun renderList(users: List<User>) {

adapter.addData(users)

adapter.notifyDataSetChanged()

}



private fun setupViewModel() {

mainViewModel = ViewModelProviders.of(

this,

ViewModelFactory(ApiHelper(ApiServiceImpl()))

).get(MainViewModel::class.java)

}

}

 

۲۷) در آخر ، مجوز اینترنت را در پروژه خود اضافه کنید. در واقع موارد زیر را در AndroidManifest.xml اضافه کنید:

<uses-permission android:name="android.permission.INTERNET"/>

۲۸) اکنون ، پروژه را بسازید و برنامه را روی دستگاه اجرا کنید. باید داده ها را در UI بارگیری کنید.