هیچ دوره ای در سبد خرید شما وجود ندارد
معماری MVVM الگوی معماری نرم شناخته شده ای است که بر تمام اشکالات الگوهای طراحی MVP و MVC غلبه دارد. MVVM پیشنهاد می کند که منطق ارائه داده ها (Views یا UI) را از بخش منطق تجاری اصلی برنامه جدا کنید. در این مقاله از سری مقالات آموزش برنامه نویسی اندروید به ساختن یک اپ به کمک معماری MVVM در اندروید و کاربرد Retrofit خواهیم پرداخت.
لایه های مجزای MVVM در اندروید
Model: این لایه وظیفه انتزاع منابع داده را بر عهده دارد. Model و ViewModel برای دریافت و ذخیره داده ها با هم کار می کنند.
View: هدف این لایه اطلاع رسانی به ViewModel در مورد فعالیت کاربر است. این لایه ViewModel را مشاهده می کند و حاوی هیچ نوع منطق برنامه نیست.
ViewModel: جریانهای دادهای را که مربوط به View هستند را نشان میدهد. علاوه بر این، به عنوان یک پیوند بین Model و View عمل می کند.
قبل از ادامه صحبتمان باید به این نکته اشاره کنم که در دوره جامع آموزش برنامه نویسی اندروید وبسایت دانجشویار بطور جامع و کاربردی معماری MVVM اندروید را بررسی و آموزش دادهایم. اگر علاقه مند به یادگیری اصول تئوری این مبحث هستید میتوانید از طریق لینک زیر به این دوره دسترسی داشته باشید.
در این مقاله می آموزیم که چگونه می توانیم با استفاده از معماری MVVM در اندروید و زبان Kotlin یک اپلیکیشن ساده برای نام و تصویر فیلمها بسازیم. برای ساخت این اپلیکیشن به معماری MVVM و Retrofit Library نیاز داریم. Retrofit کتابخانهای است که به ما در ایجاد request در اندروید کمک می کند. داده ها را از وب سایت پایگاه داده فیلم (TMDB) واکشی خواهیم کرددر این مثال، ما لیستی از فیلم های محبوب را دریافت می کنیم.
گامهای ایجاد اپ
1- در سایت https://developer.themoviedb.org/reference/movie-popular-list عضو می شویم و وارد سایت می شویم.
2- با انتخاب گزینه API Reference از قسمت بالا سمت چپ وارد صفحه مربوط به درخواست API می شویم.
3- دکمه Get API Key را کلیک میکنیم.
4- با پر کردن فرم مشخصات و پذیرش شرایط کلید API و API Read Access Token را دریافت خواهیم کرد.
5- با انتخاب زبان کاتلین در آدرس زیر مشاهده می کنیم که از تابع get() برای request استفاده میشود:
https://developer.themoviedb.org/reference/intro/getting-started
6- با جاگذاری API Key در قسمت AUTHORIZATION و فشردن دکمه Try It! و دریافت پاسخ 200 در خروجی JSON خواهیم داشت که آن را یادداشت مینماییم.
7- اکنون پروژهای جدید در اندروید استودیو ایجاد میکنیم.
8- چون میخواهیم از اینترنت استفاده کنیم سه permission زیر را در AndroidManifest مینویسیم و از کاربر آنها را دریافت میکنیم:
<uses-permission android:name=”android.permission.INTERNET”/>
<uses-permission android:name=”android.permission.ACCESS_NETWORK_STATE”/>
<uses-permission android:name=”android.permission.ACCESS_WIFI_STATE”/>
9- کتابخانه Retrofit را در فایل Build.gradle(app) اضافه می کنیم:
//add retrofit library
implementation 'com.squareup.retrofit2:retrofit:2.9.0'
implementation 'com.squareup.retrofit2:converter-gson:2.9.0'
رتروفیت (Retrofit) کتابخانهای بسیار قدرتمند جهت اتصال به سرور و ارتباط با API سمت سرور است. این کتابخانه توسط شرکت Square پشتیبانی میشود و مورد تایید گوگل است .
10- کتابخانههای View Model و Live Data را نیز به فایل فوق اضافه میکنیم:
def lifecycle_version = "2.6.0-alpha01"
implementation("androidx.lifecycle:lifecycle-viewmodel-ktx:$lifecycle_version")
implementation("androidx.lifecycle:lifecycle-viewmodel-compose:$lifecycle_version")
implementation group: 'androidx.lifecycle', name: 'lifecycle-extensions', version: '2.2.0'
LiveData به ما کمک میکند تا به جای بررسی مداوم برای تغییرات در منبع اطلاعات، هر زمان تغییری ایجاد شد به طور خودکار رابط کاربری از آن آگاهی پیدا کرده و تغییرات لازم در UI صورت گیرد. ViewModel نیز در این راه به ما کمک بسیاری میکند.
11- برای کار با تصاویر کتابخانه Glide را نیز به فایل مزبور اضافه میکنیم:
implementation 'com.github.bumptech.glide:glide:4.13.2'
12- برای فعال کردن view binding، این کد را داخل بلوک Android در فایل build.gradle(app) اضافه میکنیم:
buildFeatures {
viewBinding = true
}
13- اکنون به کمک پلاگین JSON To Kotlin Class در اندروید استودیو و با راست کلیک روی package اصلی اپ و انتخاب گزینه New و سپس زیر منوی Kotlin-data class from JSON وpaste مقدار JSON دریافتی از سایت developer.themoviedb.org درون پنجره Generate Kotlin Data Class Code دو کلاس دادهای (data class) به نامهای Movies و Result ایجاد خواهد شد. (اگر پلاگین فوق را درون اندروید استودیو ندارید به مسیر File -> Settings -> Plugins بروید و پلاگین را نصب کنید.)
data class Movies(
val page: Int,
val results: List<Result>,
val total_pages: Int,
val total_results: Int
)
data class Result(
val adult: Boolean,
val backdrop_path: String,
val genre_ids: List<Int>,
val id: Int,
val original_language: String,
val original_title: String,
val overview: String,
val popularity: Double,
val poster_path: String,
val release_date: String,
val title: String,
val video: Boolean,
val vote_average: Double,
val vote_count: Int
)
در حقیقت در کلاس بالا فقط از poster_path برای یافتن مسیر پوستر هر فیلم و title برای نمایش عنوان آن استفاده میکنیم.
14- روی package اصلی راست کلیک کرده و اینترفیس MovieApi را با توجه به گام 5 ایجاد می کنیم:
import retrofit2.Call
import retrofit2.http.GET
import retrofit2.http.Query
interface MovieApi {
@GET("popular?")
fun getPopularMovies(@Query("api_key") api_key : String) : Call<Movies>
}
در interface بالا Query با دریافت api_ky از متد get برای ارسال request استفاده میکند.
15- دوباره روی package فوق راست کلیک میکنیم و object زیر را میسازیم:
import retrofit2.Retrofit
import retrofit2.converter.gson.GsonConverterFactory
object RetrofitInstance {
val api : MovieApi by lazy {
Retrofit.Builder()
.baseUrl(
"https://api.themoviedb.org/3/movie/"
)
.addConverterFactory(GsonConverterFactory.create())
.build()
.create(MovieApi::class.java)
}
}
برای پیاده سازی الگوی طراحی Singleton از object استفاده میکنیم. در کاتلین object کلاس خاصی است که فقط یک نمونه دارد. Singleton یک الگوی طراحی است که تضمین میکند که یک کلاس فقط یک نمونه داشته باشد و یک نقطه دسترسی سراسری به شیء را فراهم میکند. این الگو باعث صرفهجویی در منابع ram میشود. در object بالا از lazy برای پیاده سازی Singleton استفاده کردهایم.
در اینجا یک متغیر val به نام api از نوع MovieApi ساخته میشود و درون این ساختار از کتابخانه Retrofit استفاده میکنیم تا به کمک interface به نام MovieApi و توسط متد get درون آن ساختاری JSON شکل دریافت کنیم و به وسیله GsonConverterFactory آن را تبدیل کنیم.
gson متدهای ساده toJson() و fromJson() را برای تبدیل اشیاء جاوا به JSON و بالعکس ارائه می کند و GsonConverterFactory مبدلی است که از Gson برای سریال سازی (serialization ) به JSON و بالعکس استفاده می کند.
16- در مسیر app > res > layout > activity_main.xml کد زیر را اضافه میکنیم تا خروجیها که شامل نام و تصویر فیلمها هستند درون یک recyclerview به نمایش درآیند:
<?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=".MainActivity">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/rv_movies"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
tools:listitem="@layout/movie_layout">
</androidx.recyclerview.widget.RecyclerView>
</androidx.constraintlayout.widget.ConstraintLayout>
17- برای recycler view یک فایل layout جدید درون پوشه res->layout به نام movie_layout ایجاد می کنیم تا حاوی ImageView و TextView برای نمایش نام و تصویر هر فیلم باشد:
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout 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">
<ImageView
android:id="@+id/movieImage"
android:layout_width="200dp"
android:layout_height="200dp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintEnd_toEndOf="parent"
android:scaleType="fitCenter"
android:src="@color/teal_200"
android:contentDescription="@string/image_of_movies"/>
<TextView
android:id="@+id/movieName"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="@id/movieImage"
android:textSize="30sp"
android:text="@string/movie_name"
android:textAlignment="center"
android:textColor="@color/black"
android:textStyle="bold"
android:layout_marginTop="5dp"/>
</androidx.constraintlayout.widget.ConstraintLayout>
18- یک کلاس Movie Adapter برای RecyclerView ایجاد می کنیم. در این کلاس از Glide برای نمایش تصویر پوستر هر فیلم استفاده میکنیم. در این کلاس از آرایه movieList استفاده میشود که آرایهای از اشیا Result هست و هر عضو آن دارای خاصیت رشتهای poster_path است که با اضافه شدن به مسیر https://image.tmdb.org/t/p/w500 مسیر تصویر پوستر فیلم در سایت را به Glide برای قراردادن در imageview مربوطه در recyclerview میدهد. خاصیت دیگر هر عضو title هست که برای متن textview مربوطه در recyclerview به کار میرود. چون با دیگر فیلدها کاری نداریم میتوانستیم هنگام تبدیل از JSON باقی فیلدهای کلاس را حذف کنیم.
import androidx.recyclerview.widget.RecyclerView
import com.bumptech.glide.Glide
import com.example.mvvmmoviedetail.databinding.MovieLayoutBinding
class MovieAdapter : RecyclerView.Adapter<MovieAdapter.ViewHolder>() {
private var movieList = ArrayList<Result>()
@SuppressLint("NotifyDataSetChanged")
fun setMovieList(movieList: List<Result>) {
this.movieList = movieList as ArrayList<Result>
notifyDataSetChanged()
}
class ViewHolder(val binding: MovieLayoutBinding) : RecyclerView.ViewHolder(binding.root) {}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
return ViewHolder(
MovieLayoutBinding.inflate(
LayoutInflater.from(
parent.context
)
)
)
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
Glide.with(holder.itemView)
.load("https://image.tmdb.org/t/p/w500" + movieList[position].poster_path)
.into(holder.binding.movieImage)
holder.binding.movieName.text = movieList[position].title
}
override fun getItemCount(): Int {
return movieList.size
}
}
19- یک کلاس View Model با live-data ایجاد میکنیم تا از معماری MVVM درون اپ خود استفاده کنیم، باید درون این اپ از apikey دریافتی استفاده کنیم و آن را به object فوق پاس دهیم. باتوجه به Response های دریافتی گزینههایی خواهیم داشت:
import android.util.Log
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import retrofit2.Call
import retrofit2.Callback
import retrofit2.Response
class MovieViewModel : ViewModel() {
private var movieLiveData = MutableLiveData<List<Result>>()
val apiKey = "69d66957eebff9666ea46bd464773cf0"
fun getPopularMovies() {
RetrofitInstance.api.getPopularMovies(apiKey).enqueue(object : Callback<Movies>{
override fun onResponse(call: Call<Movies>, response: Response<Movies>) {
if (response.body()!=null){
movieLiveData.value = response.body()!!.results
}
else{
return
}
}
override fun onFailure(call: Call<Movies>, t: Throwable) {
Log.d("TAG",t.message.toString())
}
})
}
fun observeMovieLiveData() : LiveData<List<Result>> {
return movieLiveData
}
}
20- درون MainActivity از کد زیر استفاده میکنیم. در این کد تابع prepareRecyclerView برای استفاده از فایل Adapter و تنظیم RecyclerView به کار رفته است:
import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import androidx.lifecycle.Observer
import androidx.lifecycle.ViewModelProvider
import androidx.recyclerview.widget.GridLayoutManager
class MainActivity : AppCompatActivity() {
private lateinit var binding : ActivityMainBinding
private lateinit var viewModel: MovieViewModel
private lateinit var movieAdapter : MovieAdapter
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
prepareRecyclerView()
viewModel = ViewModelProvider(this)[MovieViewModel::class.java]
viewModel.getPopularMovies()
viewModel.observeMovieLiveData().observe(this, Observer { movieList ->
movieAdapter.setMovieList(movieList)
})
}
private fun prepareRecyclerView() {
movieAdapter = MovieAdapter()
binding.rvMovies.apply {
layoutManager = GridLayoutManager(applicationContext,2)
adapter = movieAdapter
}
}
}
خروجی اپ باید به شکل زیر خواهد بود:
سوالات و پیشنهادات خود را به صورت دیدگاه مطرح کنید
ارسال دیدگاه