فهرست
الف ) درباره این کتاب
ب ) آیا این کتاب مناسب شماست؟
پ ) درباره نویسنده
ت ) چند سخن مترجم
3.1 ) ساختن پروژه در اندروید استودیو
3.3 ) تبدیل MainActivity به کد کاتلین
8 - بدست آوردن دیتا با استفاده از API
8.2 ) انجام درخواست خارج از نخ اصلی
9.3 ) مپ کردن یک آبجکت به متغیرها
10.1 ) تبدیل JSON به کلاس های دیتا
10.2 ) شکل دادن به لایه ی domain
11.3 ) عملگرها در توابع الحاقی
بقیه فصل ها در حال ترجمه است
تجزیه دیتا
تبدیل JSON به کلاس های دیتا
حالا که میدونیم چگونه کلاس های دیتا را درست کنیم، آماده ی شروعِ تجزیه اطلاعات هستیم. در پکیج data
، فایلی به نام ResponseClasses.kt
ایجاد کنید. به وسیله url
ای که در بخش 8 استفاده کردیم میتوانید ساختار فایل json
رو ببینید که تشکیل شده از یک object است که یک شهر را داخل خودش دارد و یک لیست از پیشبینی ها. شهر یک id
، یک name
، مختصات و این که متعلق به چه کشوری هست نیز را دارا میباشد. هر پیشبینی به همراه اطلاعاتی مانند date
و دماهای متفاوت و همچنین یک object از آب و هوا را داراست، که اون آبجکت توضیحات و همچنین یک id
از یک icon
رو به همراه خودش داره.
data class ForecastResult(val city: City, val list: List<Forecast>)
data class City(val id: Long, val name: String, val coord: Coordinates,
val country: String, val population: Int)
data class Coordinates(val lon: Float, val lat: Float)
data class Forecast(val dt: Long, val temp: Temperature, val pressure: Float,
val humidity: Int, val weather: List<Weather> ,
val speed: Float, val deg: Int, val clouds: Int,
val rain: Float)
data class Temperature(val day: Float, val min: Float, val max: Float,
val night: Float, val eve: Float, val morn: Float)
data class Weather(val id: Long, val main: String, val description: String,
val icon: String)
همینطور که از Gson برای تجزیه به کلاس هامون استفاده کردیم، باید در نظر داشته باشیم که خصیصه ها باید هم نامِ یکی های خود در فایل json باشند یا یک نام serialized شده را معرفی کنند. یکی از روش هایی که در اکثر معماری های برنامه نویسی توضیح داده میشود، استفاده از مدل های متفاوت برای لایه های متفاوت به جهت مجزا سازی آنها در برنامه امان است. بنابراین من ترجیح میدهم که کلاس ها را به صورت ساده declare کنم، به این دلیل که من آنها را قبل از این که در بقیه برنامه استفاده شوند تبدیل خواهم کرد. نام های خصیصه ها در اینجا دقیقا به مانند نام های موجود در json پاسخ داده شده است.
حالا، کلاس Request
برای برگرداندن جواب تجزیه شده نیاز به تغییرات دارد. همچنین برای خوانایی بیشتر، تنها نیاز به zipcode
شهر دارد و از url
کامل استفاده نخواهد شد. پس فعلا static url
را به companion object
خواهیم داد چون که شاید بعدا برای درخواست های بیشتر نیازی به آن پیدا کنیم.
Companion object
ها: کاتلین اجازه میده که ابجکت هایی رو تعریف کنیم که رفتارstatic
طوری داشته باشند. ما نمیتونیم خصیصه ها و یا توابعیstatic
درست کنیم، پس باید به object ها روی بیاریم. اگرچه این کار نحوه پیاده سازی Singleton را ساده تر خواهند کرد. اگر به تعدادی خصیصه،constant
و یا توابعstatic
در کلاس نیاز داشته باشیم، ازcompanion object
استفاده میکنیم. این ابجکت در سرتاسر نسل های ساخته شده از کلاس قابل اشتراک گذاری است، دقیقا به مانند static در جاوا.
به کد نتیجه گیری شده زیر نگاه کنید:
class ForecastRequest(val zipCode: String) {
companion object {
private val APP_ID = "15646a06818f61f7b8d7823ca833e1ce"
private val URL = "http://api.openweathermap.org/data/2.5/" +
"forecast/daily?mode=json&units=metric&cnt=7"
private val COMPLETE_URL = "$URL&APPID=$APP_ID&q="
}
fun execute(): ForecastResult {
val forecastJsonStr = URL(COMPLETE_URL + zipCode).readText()
return Gson().fromJson(forecastJsonStr, ForecastResult::class.java)
}
}
فراموش نکنید که کتابخانه Gson را به build.gradle
اضافه کنید
compile "com.google.code.gson:gson:2.4"
شکل دادن به لایه ی domain
حالا نوبت ساختن پکیج نمایش دهنده ی لایه ی domain
هه که شامل چند command
هه! ولی برای شروع اول باید command
رو تعریف کنیم.
public interface Command<T> {
fun execute(): T
}
این دستورات (command
) یک فعالیت رو اجرا میکنن و یک آبجکت رو که جنس کلاسش به صورت generic type
مشخص شده رو برمیگردونن. یکی از ویژگی های جالب اینه که هر تابعی در کاتلین یک مقدار برمیگردونه! به صورت پیشفرض، اگر جنس مقداری که برگردانده خواهد شد مشخص نبود، یک آبجکت از جنس کلاس Unit
رو برمیگدونه. بنابراین اگر میخوایم که command
مون چیزی برنگردونه، میتونیم جنسش رو Unit
نیز معرفی کنیم.
Interface
ها در کاتلین، قدرتمند تر از جاوا هستند چون میتونن شامل کد هم بشن ولی برای فعلا ما کاری به این ویژگی نداریم. در فصل های آینده بیشتر در مورد این موضوع توضیح خواهیم داد.
در دستور اول، نیازه که اول پیشبینی رو از API درخواست کنیم و بعد به کلاس domain
تبدیل کنیمش. کد کلاس های domain
رو در زیر ببینیم :
data class ForecastList(val city: String, val country: String,
val dailyForecast:List<Forecast>)
data class Forecast(val date: String, val description: String, val high: Int,
val low: Int)
حالا احتمالا این کلاس ها رو بعدا وقتی ویژگی های دیگه ای اضافه شد، بازدید میکنیم ولی فعلا برای دیتایی که میخوایم نگه داریم کافیه.
کلاس ها باید از data
به مدل domain
مپ بشن، بنابراین فعالیت بعدی ساخت DataMapper
اهه.
public class ForecastDataMapper {
fun convertFromDataModel(forecast: ForecastResult): ForecastList {
return ForecastList(forecast.city.name, forecast.city.country,
convertForecastListToDomain(forecast.list))
}
private fun convertForecastListToDomain(list: List<Forecast>):
List<ModelForecast> {
return list.map { convertForecastItemToDomain(it) }
}
private fun convertForecastItemToDomain(forecast: Forecast): ModelForecast {
return ModelForecast(convertDate(forecast.dt),
forecast.weather[0].description, forecast.temp.max.toInt(),
forecast.temp.min.toInt())
}
private fun convertDate(date: Long): String {
val df = DateFormat.getDateInstance(DateFormat.MEDIUM,
Locale.getDefault())
return df.format(date * 1000)
}
}
در این حین که داریم از دو کلاس با نام های یکسان استفاده میکنیم، باید به یکی، نام مشخص و مجزایی بدیم تا نیازی به نوشتن نام کامل پکیج نداشته باشیم
import com.antonioleiva.weatherapp.domain.model.Forecast as ModelForecast
یکی از بخش های جالب این کد، نحوه ی تبدیل لیست پیشبینی، از data
به مدل domain
است.
return list.map { convertForecastItemToDomain(it) }
در یک خط، ما میتونیم بر روی یک collection حلقه ای رو تکرار کنیم و یک لیست جدید با ایتم های کانورت شده برگردونیم. کاتلین یک سری از توابع اعمالی خوبی بر روی لیست ها مهیا کرده که یک عمل را بر روی تمامی ایتم های لیست اجرا میکند و به نحو دلخواه انتقال میده! این یکی از قدرتمندترین ویژگی های کاتلین برای توسعه دهندگانی است که از java 7 استفاده میکردند. به زودی یک نگاهی به این توابع خواهیم انداخت. این مهمه که بدونیم آنها وجود دارن، زیرا که مواقع زیادی پیدا میشوند که استفاده از این توابع باعث صرفه جویی در زمان و خط های بیهوده میشود.
و حالا همه چی برای نوشتن دستور مناسب است:
class RequestForecastCommand(val zipCode: String) :
Command<ForecastList> {
override fun execute(): ForecastList {
val forecastRequest = ForecastRequest(zipCode)
return ForecastDataMapper().convertFromDataModel(
forecastRequest.execute())
}
}
نمایش دیتا بر روی UI
کد MainActivity
مقداری تغییر خواهد کرد، زیرا که ما حالا دیتای واقعی برای پر کردن آداپتور خواهیم داشت. برای این باید درخواست asynchronous امون رو دوباره بنویسیم
async() {
val result = RequestForecastCommand("94043").execute()
uiThread {
forecastList.adapter = ForecastListAdapter(result)
}
}
آداپتور هم نیاز به مقداری تغییر دارد
class ForecastListAdapter(val weekForecast: ForecastList) :
RecyclerView.Adapter<ForecastListAdapter.ViewHolder>() {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int):
ViewHolder? {
return ViewHolder(TextView(parent.getContext()))
}
override fun onBindViewHolder(holder: ViewHolder,
position: Int) {
with(weekForecast.dailyForecast[position]) {
holder.textView.text = "$date - $description - $high/$low"
}
}
override fun getItemCount(): Int = weekForecast.dailyForecast.size
class ViewHolder(val textView: TextView) : RecyclerView.ViewHolder(textView)
}
- تابع with: یکی از توابع سودمندی که در کتابخانه استاندارد کاتلین اضافه شده است، تابع with است. به صورت خیلی ساده، این تابع یک آبجکت و یک تابع الحاقی رو به صورت پارامترهای ورودی دریافت میکند و آبجکت، آن تابع را اجرا خواهد کرد. این به این معنی است که تمام کدهایی که داخل براکت ها نوشته میشوند به صورت تابع الحاقی بر روی آن ابجکتی که به عنوان پارامتر اول دادیم، اجرا خواهند شد.