این کد لبه بخشی از دوره آموزشی Android Kotlin Fundamentals است. اگر به ترتیب روی کدها کار کنید، بیشترین ارزش را از این دوره خواهید گرفت. همه کد لبه های دوره در صفحه فرود کد لبه های Android Kotlin Fundamentals فهرست شده اند.
صفحه عنوان |
صفحه نمایش بازی |
صفحه نمایش امتیاز |
مقدمه
در این کد لبه، با یکی از اجزای معماری اندروید، ViewModel آشنا می شوید:
- شما از کلاس
ViewModelبرای ذخیره و مدیریت داده های مرتبط با رابط کاربری به روشی آگاهانه از چرخه حیات استفاده می کنید. کلاسViewModelبه داده ها اجازه می دهد تا از تغییرات پیکربندی دستگاه مانند چرخش صفحه و تغییرات در دسترس بودن صفحه کلید زنده بمانند. - شما از کلاس
ViewModelFactoryبرای نمونه سازی و برگرداندن شیViewModelکه از تغییرات پیکربندی جان سالم به در می برد استفاده می کنید.
آنچه از قبل باید بدانید
- نحوه ایجاد برنامه های اساسی اندروید در Kotlin.
- نحوه استفاده از نمودار ناوبری برای پیاده سازی ناوبری در برنامه خود.
- نحوه اضافه کردن کد برای پیمایش بین مقصدهای برنامه و ارسال داده ها بین مقصدهای ناوبری.
- نحوه کار چرخه های عمر فعالیت و قطعه
- چگونه با استفاده از Logcat در اندروید استودیو اطلاعات لاگ را به برنامه اضافه کنیم و لاگ ها را بخوانیم.
چیزی که یاد خواهید گرفت
- نحوه استفاده از معماری برنامه اندروید توصیه شده
- چگونه از کلاس های
Lifecycle،ViewModelوViewModelFactoryدر برنامه خود استفاده کنید. - نحوه حفظ داده های رابط کاربری از طریق تغییرات پیکربندی دستگاه
- الگوی طراحی روش کارخانه چیست و چگونه از آن استفاده کنیم.
- نحوه ایجاد یک شی
ViewModelبا استفاده از رابطViewModelProvider.Factory.
کاری که خواهی کرد
- یک
ViewModelبه برنامه اضافه کنید تا داده های برنامه ذخیره شود تا داده ها از تغییرات پیکربندی جان سالم به در ببرند. - از
ViewModelFactoryو الگوی طراحی با روش کارخانه برای نمونه سازی یک شیViewModelبا پارامترهای سازنده استفاده کنید.
در بخش کدهای درس 5، برنامه GuessTheWord را توسعه می دهید که با کد شروع شروع می شود. GuessTheWord یک بازی دو نفره به سبک charades است که در آن بازیکنان برای دستیابی به بالاترین امتیاز ممکن با یکدیگر همکاری می کنند.
بازیکن اول به کلمات موجود در برنامه نگاه می کند و هر کدام را به نوبه خود عمل می کند و مطمئن می شود که کلمه را به بازیکن دوم نشان نمی دهد. بازیکن دوم سعی می کند کلمه را حدس بزند.
برای انجام بازی، اولین بازیکن برنامه را روی دستگاه باز می کند و کلمه ای را می بیند، به عنوان مثال "گیتار"، همانطور که در تصویر زیر نشان داده شده است.
اولین بازیکن کلمه را اجرا می کند و مراقب است که در واقع خود کلمه را نگوید.
- وقتی بازیکن دوم کلمه را به درستی حدس زد، بازیکن اول دکمه Got It را فشار می دهد که تعداد را یک عدد افزایش می دهد و کلمه بعدی را نشان می دهد.
- اگر بازیکن دوم نتواند کلمه را حدس بزند، بازیکن اول دکمه Skip را فشار می دهد که تعداد را یک کاهش می دهد و به کلمه بعدی می رود.
- برای پایان بازی، دکمه پایان بازی را فشار دهید. (این قابلیت در کد شروع برای اولین آزمایشگاه کد در این سری نیست.)
در این کار، برنامه استارتر را دانلود و اجرا می کنید و کد را بررسی می کنید.
مرحله 1: شروع کنید
- کد شروع GuessTheWord را دانلود کرده و پروژه را در Android Studio باز کنید.
- برنامه را روی یک دستگاه مجهز به اندروید یا شبیه ساز اجرا کنید.
- روی دکمه ها ضربه بزنید. توجه داشته باشید که دکمه Skip کلمه بعدی را نمایش می دهد و امتیاز را یک بار کاهش می دهد و دکمه Got It کلمه بعدی را نشان می دهد و امتیاز را یک افزایش می دهد. دکمه پایان بازی اجرا نمی شود، بنابراین با ضربه زدن روی آن هیچ اتفاقی نمی افتد.
مرحله 2: یک مرور کد انجام دهید
- در اندروید استودیو، کد را کاوش کنید تا از نحوه عملکرد برنامه مطلع شوید.
- مطمئن شوید که به فایلهایی که در زیر توضیح داده شدهاند، که اهمیت ویژهای دارند، نگاه کنید.
MainActivity.kt
این فایل فقط حاوی کدهای پیشفرض و ایجاد شده توسط الگو است.
res/layout/main_activity.xml
این فایل حاوی طرح اصلی برنامه است. NavHostFragment سایر قطعات را در حین حرکت کاربر در برنامه میزبانی می کند.
قطعات UI
کد شروع دارای سه قطعه در سه بسته مختلف در زیر بسته com.example.android.guesstheword.screens است:
-
title/TitleFragmentبرای صفحه عنوان -
game/GameFragmentبرای صفحه بازی -
score/ScoreFragmentبرای صفحه نمایش امتیاز
screens/title/TitleFragment.kt
قطعه عنوان اولین صفحه ای است که هنگام راه اندازی برنامه نمایش داده می شود. یک کنترل کننده کلیک روی دکمه Play تنظیم شده است تا به صفحه بازی بروید.
screens/game/GameFragment.kt
این قطعه اصلی است که بیشتر اکشن های بازی در آن انجام می شود:
- متغیرهایی برای کلمه فعلی و نمره فعلی تعریف شده است.
-
wordListتعریف شده در متدresetList()یک لیست نمونه از کلماتی است که در بازی استفاده می شود. - متد
onSkip()کنترل کننده کلیک برای دکمه Skip است. امتیاز را 1 کاهش می دهد، سپس کلمه بعدی را با استفاده از متدnextWord()نمایش می دهد. - متد
onCorrect()کنترل کننده کلیک برای دکمه Got It است. این متد مشابه متدonSkip()پیاده سازی می شود. تنها تفاوت این است که این روش به جای کم کردن 1 به امتیاز اضافه می کند.
screens/score/ScoreFragment.kt
ScoreFragment صفحه آخر بازی است و امتیاز نهایی بازیکن را نمایش می دهد. در این کد لبه، پیاده سازی را اضافه می کنید تا این صفحه نمایش داده شود و امتیاز نهایی نمایش داده شود.
res/navigation/main_navigation.xml
نمودار ناوبری نحوه اتصال قطعات از طریق ناوبری را نشان می دهد:
- از قسمت عنوان، کاربر می تواند به قطعه بازی حرکت کند.
- از قطعه بازی، کاربر می تواند به قطعه امتیاز حرکت کند.
- از قسمت امتیاز، کاربر می تواند به قطعه بازی برگردد.
در این کار، مشکلات مربوط به برنامه شروع GuessTheWord را پیدا می کنید.
- کد شروع را اجرا کنید و بازی را با چند کلمه اجرا کنید، روی Skip یا Got It بعد از هر کلمه ضربه بزنید.
- صفحه بازی اکنون یک کلمه و امتیاز فعلی را نشان می دهد. جهت صفحه نمایش را با چرخاندن دستگاه یا شبیه ساز تغییر دهید. توجه داشته باشید که امتیاز فعلی از دست رفته است.
- بازی را با چند کلمه دیگر اجرا کنید. وقتی صفحه بازی با مقداری امتیاز نمایش داده شد، برنامه را ببندید و دوباره باز کنید. توجه داشته باشید که بازی از ابتدا ریستارت می شود، زیرا وضعیت برنامه ذخیره نشده است.
- بازی را با چند کلمه اجرا کنید، سپس روی دکمه پایان بازی ضربه بزنید. توجه کنید که هیچ اتفاقی نمی افتد.
مشکلات برنامه:
- برنامه شروع کننده در طول تغییرات پیکربندی، مانند زمانی که جهت دستگاه تغییر می کند، یا زمانی که برنامه خاموش می شود و دوباره راه اندازی می شود، وضعیت برنامه را ذخیره و بازیابی نمی کند.
می توانید این مشکل را با استفاده از callbackonSaveInstanceState()حل کنید. با این حال، استفاده از متدonSaveInstanceState()مستلزم نوشتن کد اضافی برای ذخیره وضعیت در یک بسته و پیاده سازی منطق برای بازیابی آن حالت است. همچنین، مقدار داده ای که می توان ذخیره کرد حداقل است. - وقتی کاربر روی دکمه پایان بازی ضربه میزند، صفحه بازی به صفحه امتیاز حرکت نمیکند.
میتوانید این مشکلات را با استفاده از اجزای معماری برنامهای که در این نرمافزار یاد میگیرید حل کنید.
معماری اپلیکیشن
معماری برنامه راهی برای طراحی کلاس های برنامه های شما و روابط بین آنها است، به گونه ای که کد سازماندهی شده است، در سناریوهای خاص به خوبی عمل می کند و کار با آن آسان است. در این مجموعه چهار کد، بهبودهایی که در برنامه GuessTheWord ایجاد میکنید از دستورالعملهای معماری برنامه اندروید پیروی میکنند و از اجزای معماری Android استفاده میکنید. معماری اپلیکیشن اندروید مشابه الگوی معماری MVVM (model-view-viewmodel) است.
برنامه GuessTheWord از اصل طراحی جداسازی نگرانیها پیروی میکند و به کلاسهایی تقسیم میشود که هر کلاس به نگرانی جداگانهای میپردازد. در این اولین کد لبه درس، کلاس هایی که با آنها کار می کنید یک کنترلر UI، یک ViewModel و یک ViewModelFactory هستند.
کنترلر رابط کاربری
یک کنترلر UI یک کلاس مبتنی بر UI مانند Activity یا Fragment است. یک کنترلر UI فقط باید دارای منطقی باشد که تعاملات رابط کاربری و سیستم عامل مانند نمایش نماها و گرفتن ورودی کاربر را کنترل کند. منطق تصمیم گیری، مانند منطقی که متن مورد نظر را تعیین می کند، در کنترلر رابط کاربری قرار ندهید.
در کد شروع GuessTheWord، کنترلکنندههای رابط کاربری سه بخش هستند: GameFragment ، ScoreFragment, و TitleFragment . با پیروی از اصل طراحی «جداسازی نگرانیها»، GameFragment تنها وظیفه ترسیم عناصر بازی روی صفحه و دانستن زمانی که کاربر روی دکمهها ضربه میزند را بر عهده دارد و نه بیشتر. وقتی کاربر روی دکمه ای ضربه می زند، این اطلاعات به GameViewModel منتقل می شود.
ViewModel
یک ViewModel داده هایی را برای نمایش در یک قطعه یا فعالیت مرتبط با ViewModel نگه می دارد. یک ViewModel می تواند محاسبات و تبدیل های ساده ای را روی داده ها انجام دهد تا داده ها را برای نمایش توسط کنترلر UI آماده کند. در این معماری، ViewModel تصمیم گیری را انجام می دهد.
GameViewModel داده هایی مانند مقدار امتیاز، لیست کلمات و کلمه فعلی را نگه می دارد، زیرا این داده هایی است که روی صفحه نمایش داده می شود. GameViewModel همچنین حاوی منطق تجاری برای انجام محاسبات ساده برای تصمیم گیری در مورد وضعیت فعلی داده ها است.
ViewModelFactory
یک ViewModelFactory اشیاء ViewModel با یا بدون پارامترهای سازنده نمونهسازی میکند.

در کدهای بعدی، با سایر مؤلفههای معماری Android مرتبط با کنترلکنندههای رابط کاربری و ViewModel آشنا میشوید.
کلاس ViewModel برای ذخیره و مدیریت داده های مربوط به UI طراحی شده است. در این برنامه، هر ViewModel با یک قطعه مرتبط است.
در این کار، اولین ViewModel خود را به برنامه خود اضافه می کنید، GameViewModel برای GameFragment . همچنین می آموزید که به چه معناست که ViewModel از چرخه حیات آگاه است.
مرحله 1: کلاس GameViewModel را اضافه کنید
- فایل
build.gradle(module:app)را باز کنید. در داخل بلوکdependencies، وابستگی Gradle را برایViewModelاضافه کنید.
اگر از آخرین نسخه کتابخانه استفاده می کنید، برنامه راه حل باید همانطور که انتظار می رود کامپایل شود. اگر نشد، سعی کنید مشکل را حل کنید یا به نسخه نشان داده شده در زیر برگردید.
//ViewModel
implementation 'androidx.lifecycle:lifecycle-extensions:2.0.0'- در پوشه بسته
screens/game/یک کلاس Kotlin جدید به نامGameViewModelایجاد کنید. - کلاس
GameViewModelرا گسترش دهید تا کلاس انتزاعیViewModelگسترش دهد. - برای کمک به درک بهتر اینکه
ViewModelچگونه از چرخه حیات آگاه است، یک بلوکinitبا یک دستورlogاضافه کنید.
class GameViewModel : ViewModel() {
init {
Log.i("GameViewModel", "GameViewModel created!")
}
}مرحله 2: onCleared() را لغو کنید و لاگ را اضافه کنید
ViewModel زمانی که قطعه مرتبط جدا می شود یا زمانی که فعالیت به پایان می رسد از بین می رود. درست قبل از اینکه ViewModel از بین برود، فراخوانی onCleared() برای پاکسازی منابع فراخوانی می شود.
- در کلاس
GameViewModel، متدonCleared()لغو کنید. - برای ردیابی چرخه عمر
GameViewModelیک بیانیه log در داخلonCleared()اضافه کنید.
override fun onCleared() {
super.onCleared()
Log.i("GameViewModel", "GameViewModel destroyed!")
}مرحله 3: GameViewModel را با قطعه بازی مرتبط کنید
یک ViewModel باید با یک کنترلر UI مرتبط شود. برای مرتبط کردن این دو، یک مرجع به ViewModel در داخل کنترلر UI ایجاد می کنید.
در این مرحله، شما یک مرجع از GameViewModel را در داخل کنترلر UI مربوطه ایجاد می کنید که GameFragment است.
- در کلاس
GameFragment، یک فیلد از نوعGameViewModelدر سطح بالا به عنوان متغیر کلاس اضافه کنید.
private lateinit var viewModel: GameViewModelمرحله 4: ViewModel را راه اندازی کنید
در طول تغییرات پیکربندی مانند چرخش صفحه، کنترلرهای UI مانند قطعات دوباره ایجاد می شوند. با این حال، نمونه های ViewModel زنده می مانند. اگر نمونه ViewModel با استفاده از کلاس ViewModel ایجاد کنید، هر بار که قطعه دوباره ایجاد می شود یک شی جدید ایجاد می شود. در عوض، نمونه ViewModel با استفاده از ViewModelProvider ایجاد کنید.

نحوه عملکرد ViewModelProvider :
-
ViewModelProviderیکViewModelموجود را در صورت وجود برمیگرداند، یا اگر قبلاً وجود نداشته باشد، یک مدل جدید ایجاد میکند. -
ViewModelProviderیک نمونهViewModelرا در ارتباط با محدوده داده شده (یک فعالیت یا یک قطعه) ایجاد می کند. -
ViewModelایجاد شده تا زمانی که scope زنده است حفظ می شود. به عنوان مثال، اگر محدوده یک قطعه باشد،ViewModelتا زمانی که قطعه جدا نشود حفظ می شود.
ViewModel با استفاده از روش ViewModelProviders.of() برای ایجاد ViewModelProvider راه اندازی کنید:
- در کلاس
GameFragment، متغیرviewModelمقداردهی اولیه کنید. این کد را بعد از تعریف متغیر binding داخلonCreateView()قرار دهید. از متدViewModelProviders.of()استفاده کنید و در زمینهGameFragmentمرتبط و کلاسGameViewModelعبور دهید. - بالای مقداردهی اولیه شی
ViewModel، یک دستور log اضافه کنید تا فراخوانی متدViewModelProviders.of()را ثبت کنید.
Log.i("GameFragment", "Called ViewModelProviders.of")
viewModel = ViewModelProviders.of(this).get(GameViewModel::class.java)- برنامه را اجرا کنید. در Android Studio، پنجره Logcat را باز کنید و روی
Gameفیلتر کنید. روی دکمه Play در دستگاه یا شبیه ساز خود ضربه بزنید. صفحه بازی باز می شود.
همانطور که در Logcat نشان داده شده است، متدonCreateView()GameFragmentمتدViewModelProviders.of()را برای ایجادGameViewModelفراخوانی می کند. عبارات گزارشی که بهGameFragmentوGameViewModelاضافه کردید در Logcat نشان داده می شوند.

- تنظیم چرخش خودکار را در دستگاه یا شبیه ساز خود فعال کنید و جهت صفحه را چند بار تغییر دهید.
GameFragmentهر بار از بین می رود و دوباره ایجاد می شود، بنابراینViewModelProviders.of()هر بار فراخوانی می شود. اماGameViewModelفقط یک بار ایجاد می شود و برای هر تماس دوباره ایجاد یا از بین نمی رود.
I/GameFragment: Called ViewModelProviders.of I/GameViewModel: GameViewModel created! I/GameFragment: Called ViewModelProviders.of I/GameFragment: Called ViewModelProviders.of I/GameFragment: Called ViewModelProviders.of
- از بازی خارج شوید یا از قطعه بازی خارج شوید.
GameFragmentنابود می شود.GameViewModelمرتبط نیز از بین می رود و callbackonCleared()فراخوانی می شود.
I/GameFragment: Called ViewModelProviders.of I/GameViewModel: GameViewModel created! I/GameFragment: Called ViewModelProviders.of I/GameFragment: Called ViewModelProviders.of I/GameFragment: Called ViewModelProviders.of I/GameViewModel: GameViewModel destroyed!
ViewModel از تغییرات پیکربندی جان سالم به در میبرد، بنابراین مکان مناسبی برای دادههایی است که نیاز به تغییرات پیکربندی دارند:
- داده هایی را برای نمایش روی صفحه و کدی را برای پردازش آن داده ها در
ViewModelقرار دهید. -
ViewModelهرگز نباید حاوی ارجاع به قطعات، فعالیتها یا نماها باشد، زیرا فعالیتها، قطعات و نماها از تغییرات پیکربندی دوام نمیآورند.

برای مقایسه، قبل از اینکه ViewModel اضافه کنید و بعد از اضافه کردن ViewModel ، داده های GameFragment UI را چگونه در برنامه شروع کار می کنند:
- قبل از اینکه
ViewModelاضافه کنید:
هنگامی که برنامه از طریق یک تغییر پیکربندی مانند چرخش صفحه انجام می شود، قطعه بازی از بین می رود و دوباره ایجاد می شود. داده ها از بین رفته است. - پس از اینکه
ViewModelرا اضافه کردید و داده های رابط کاربری قطعه بازی را بهViewModelمنتقل کردید:
تمام داده هایی که قطعه باید نمایش دهد اکنونViewModelاست. هنگامی که برنامه از طریق تغییر پیکربندی انجام می شود،ViewModelزنده می ماند و داده ها حفظ می شوند.

در این کار، داده های رابط کاربری برنامه را به همراه روش های پردازش داده ها به کلاس GameViewModel منتقل می کنید. شما این کار را انجام می دهید تا داده ها در طول تغییرات پیکربندی حفظ شوند.
مرحله 1: فیلدهای داده و پردازش داده ها را به ViewModel منتقل کنید
فیلدهای داده و متدهای زیر را از GameFragment به GameViewModel منتقل کنید:
- فیلدهای داده
word،scoreوwordListرا جابه جا کنید. مطمئن شوید کهwordوscoreprivateنیستند.
متغیر binding،GameFragmentBindingجابه جا نکنید، زیرا حاوی ارجاعاتی به نماها است. این متغیر برای افزایش طرح، تنظیم شنوندگان کلیک و نمایش داده ها بر روی صفحه استفاده می شود - مسئولیت های قطعه. - متدهای
resetList()وnextWord()را جابه جا کنید. این روش ها تصمیم می گیرند که چه کلمه ای روی صفحه نمایش داده شود. - از داخل متد
onCreateView()، فراخوانی های متد را بهresetList()وnextWord()به بلوکinitGameViewModelمنتقل کنید.
این متدها باید در بلوکinitباشند، زیرا باید لیست کلمات را هنگام ایجادViewModelبازنشانی کنید، نه هر بار که قطعه ایجاد می شود. می توانید عبارت log را در بلوکinitGameFragmentحذف کنید.
کنترل کننده های کلیک onSkip() و onCorrect() در GameFragment حاوی کدهایی برای پردازش داده ها و به روز رسانی رابط کاربری هستند. کد بهروزرسانی رابط کاربری باید در قطعه باقی بماند، اما کد پردازش دادهها باید به ViewModel منتقل شود.
در حال حاضر، روش های یکسان را در هر دو مکان قرار دهید:
- متدهای
onSkip()وonCorrect()را ازGameFragmentدرGameViewModelکپی کنید. - در
GameViewModel، مطمئن شوید که متدهایonSkip()وonCorrect()privateنیستند، زیرا به این متدها از قطعه ارجاع خواهید داد.
در اینجا کد کلاس GameViewModel پس از refactoring آمده است:
class GameViewModel : ViewModel() {
// The current word
var word = ""
// The current score
var score = 0
// The list of words - the front of the list is the next word to guess
private lateinit var wordList: MutableList<String>
/**
* Resets the list of words and randomizes the order
*/
private fun resetList() {
wordList = mutableListOf(
"queen",
"hospital",
"basketball",
"cat",
"change",
"snail",
"soup",
"calendar",
"sad",
"desk",
"guitar",
"home",
"railway",
"zebra",
"jelly",
"car",
"crow",
"trade",
"bag",
"roll",
"bubble"
)
wordList.shuffle()
}
init {
resetList()
nextWord()
Log.i("GameViewModel", "GameViewModel created!")
}
/**
* Moves to the next word in the list
*/
private fun nextWord() {
if (!wordList.isEmpty()) {
//Select and remove a word from the list
word = wordList.removeAt(0)
}
updateWordText()
updateScoreText()
}
/** Methods for buttons presses **/
fun onSkip() {
if (!wordList.isEmpty()) {
score--
}
nextWord()
}
fun onCorrect() {
if (!wordList.isEmpty()) {
score++
}
nextWord()
}
override fun onCleared() {
super.onCleared()
Log.i("GameViewModel", "GameViewModel destroyed!")
}
} در اینجا کد کلاس GameFragment ، پس از refactoring آمده است:
/**
* Fragment where the game is played
*/
class GameFragment : Fragment() {
private lateinit var binding: GameFragmentBinding
private lateinit var viewModel: GameViewModel
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?): View? {
// Inflate view and obtain an instance of the binding class
binding = DataBindingUtil.inflate(
inflater,
R.layout.game_fragment,
container,
false
)
Log.i("GameFragment", "Called ViewModelProviders.of")
viewModel = ViewModelProviders.of(this).get(GameViewModel::class.java)
binding.correctButton.setOnClickListener { onCorrect() }
binding.skipButton.setOnClickListener { onSkip() }
updateScoreText()
updateWordText()
return binding.root
}
/** Methods for button click handlers **/
private fun onSkip() {
if (!wordList.isEmpty()) {
score--
}
nextWord()
}
private fun onCorrect() {
if (!wordList.isEmpty()) {
score++
}
nextWord()
}
/** Methods for updating the UI **/
private fun updateWordText() {
binding.wordText.text = word
}
private fun updateScoreText() {
binding.scoreText.text = score.toString()
}
}مرحله 2: مراجع را برای کنترل کننده های کلیک و فیلدهای داده در GameFragment به روز کنید
- در
GameFragment، متدهایonSkip()وonCorrect()به روز کنید. کد را برای به روز رسانی امتیاز حذف کنید و در عوض متدهایonSkip()وonCorrect()مربوطه را درviewModelفراخوانی کنید. - از آنجایی که متد
nextWord()را بهViewModelمنتقل کردید، قطعه بازی دیگر نمی تواند به آن دسترسی داشته باشد.
درGameFragment، در متدهایonSkip()وonCorrect()، فراخوانی بهnextWord()باupdateScoreText()وupdateWordText()جایگزین کنید. این روش ها داده ها را روی صفحه نمایش می دهند.
private fun onSkip() {
viewModel.onSkip()
updateWordText()
updateScoreText()
}
private fun onCorrect() {
viewModel.onCorrect()
updateScoreText()
updateWordText()
}- در
GameFragment، متغیرهایscoreوwordرا برای استفاده از متغیرهایGameViewModelبه روز کنید، زیرا این متغیرها اکنون درGameViewModelهستند.
private fun updateWordText() {
binding.wordText.text = viewModel.word
}
private fun updateScoreText() {
binding.scoreText.text = viewModel.score.toString()
}- در
GameViewModel، در متدnextWord()فراخوانی متدهایupdateWordText()وupdateScoreText()حذف کنید. اکنون این متدها ازGameFragmentفراخوانی می شوند. - برنامه را بسازید و مطمئن شوید که هیچ خطایی وجود ندارد. اگر خطا دارید، پروژه را تمیز و بازسازی کنید.
- برنامه را اجرا کنید و بازی را از طریق چند کلمه انجام دهید. در حالی که در صفحه بازی هستید، دستگاه را بچرخانید. توجه داشته باشید که نمره فعلی و کلمه فعلی پس از تغییر جهت حفظ می شوند.
کار عالی! اکنون تمام داده های برنامه شما در ViewModel ذخیره می شود، بنابراین در طول تغییرات پیکربندی حفظ می شود.
در این کار، شنونده کلیک را برای دکمه پایان بازی پیاده سازی می کنید.
- در
GameFragment، متدی به نامonEndGame()اضافه کنید. متدonEndGame()زمانی فراخوانی می شود که کاربر روی دکمه پایان بازی ضربه بزند.
private fun onEndGame() {
}- در
GameFragment، در داخل متدonCreateView()، کدی را پیدا کنید که شنوندگان کلیک را برای دکمههای Got It و Skip تنظیم میکند. درست در زیر این دو خط، یک کلیک شنونده برای دکمه پایان بازی تنظیم کنید. از متغیر binding،bindingاستفاده کنید. در داخل شنونده کلیک، متدonEndGame()فراخوانی کنید.
binding.endGameButton.setOnClickListener { onEndGame() }- در
GameFragment، روشی به نامgameFinished()اضافه کنید تا برنامه را به صفحه امتیاز هدایت کنید. با استفاده از Safe Args ، امتیاز را به عنوان استدلال ارسال کنید.
/**
* Called when the game is finished
*/
private fun gameFinished() {
Toast.makeText(activity, "Game has just finished", Toast.LENGTH_SHORT).show()
val action = GameFragmentDirections.actionGameToScore()
action.score = viewModel.score
NavHostFragment.findNavController(this).navigate(action)
}- در متد
onEndGame()، متدgameFinished()فراخوانی کنید.
private fun onEndGame() {
gameFinished()
}- برنامه را اجرا کنید، بازی را انجام دهید و برخی از کلمات را مرور کنید. روی دکمه پایان بازی ضربه بزنید. توجه داشته باشید که برنامه به صفحه امتیاز حرکت می کند، اما امتیاز نهایی نمایش داده نمی شود. شما این را در کار بعدی حل می کنید.
|
|
وقتی کاربر بازی را تمام می کند، ScoreFragment امتیاز را نشان نمی دهد. شما یک ViewModel می خواهید که امتیاز نمایش داده شده توسط ScoreFragment را نگه دارد. در طول مقداردهی اولیه ViewModel با استفاده از الگوی روش کارخانه، مقدار امتیاز را دریافت خواهید کرد.
الگوی روش کارخانه یک الگوی طراحی خلاقانه است که از روش های کارخانه برای ایجاد اشیا استفاده می کند. متد کارخانه ای متدی است که نمونه ای از همان کلاس را برمی گرداند.
در این کار، یک ViewModel با یک سازنده پارامتری برای قطعه امتیاز و یک روش کارخانه برای نمونه سازی ViewModel ایجاد می کنید.
- تحت بسته
score، یک کلاس Kotlin جدید به نامScoreViewModelایجاد کنید. این کلاسViewModelبرای قطعه امتیاز خواهد بود. - کلاس
ScoreViewModelرا ازViewModel.یک پارامتر سازنده برای نمره نهایی اضافه کنید. یک بلوکinitبا یک دستور log اضافه کنید. - در کلاس
ScoreViewModelمتغیری به نامscoreاضافه کنید تا امتیاز نهایی ذخیره شود.
class ScoreViewModel(finalScore: Int) : ViewModel() {
// The final score
var score = finalScore
init {
Log.i("ScoreViewModel", "Final score is $finalScore")
}
}- در زیر بسته
score، یک کلاس Kotlin دیگر به نامScoreViewModelFactoryایجاد کنید. این کلاس مسئول نمونه سازی شیScoreViewModelخواهد بود. - کلاس
ScoreViewModelFactoryازViewModelProvider.Factoryگسترش دهید. یک پارامتر سازنده برای نمره نهایی اضافه کنید.
class ScoreViewModelFactory(private val finalScore: Int) : ViewModelProvider.Factory {
}- در
ScoreViewModelFactory، Android Studio خطایی را در مورد یک عضو انتزاعی اجرا نشده نشان می دهد. برای رفع خطا، متدcreate()لغو کنید. در متدcreate()، شیScoreViewModelجدید ساخته شده را برگردانید.
override fun <T : ViewModel?> create(modelClass: Class<T>): T {
if (modelClass.isAssignableFrom(ScoreViewModel::class.java)) {
return ScoreViewModel(finalScore) as T
}
throw IllegalArgumentException("Unknown ViewModel class")
}- در
ScoreFragment، متغیرهای کلاس را برایScoreViewModelوScoreViewModelFactoryایجاد کنید.
private lateinit var viewModel: ScoreViewModel
private lateinit var viewModelFactory: ScoreViewModelFactory- در
ScoreFragment، داخلonCreateView()، پس از مقداردهی اولیه متغیرbinding،viewModelFactoryمقداردهی اولیه کنید. ازScoreViewModelFactoryاستفاده کنید. امتیاز نهایی را از بسته آرگومان به عنوان پارامتر سازنده بهScoreViewModelFactory()منتقل کنید.
viewModelFactory = ScoreViewModelFactory(ScoreFragmentArgs.fromBundle(arguments!!).score)- در
onCreateView()، پس از مقداردهی اولیهviewModelFactory، شیviewModelمقداردهی اولیه کنید. متدViewModelProviders.of()را فراخوانی کنید، در زمینه قطعه امتیاز مرتبط وviewModelFactoryعبور دهید. این شیءScoreViewModelبا استفاده از متد کارخانه ای تعریف شده در کلاسviewModelFactoryایجاد می کند.
viewModel = ViewModelProviders.of(this, viewModelFactory)
.get(ScoreViewModel::class.java)- در متد
onCreateView()پس از مقداردهی اولیهviewModel، متن نمایscoreTextرا روی امتیاز نهایی تعریف شده درScoreViewModelقرار دهید.
binding.scoreText.text = viewModel.score.toString()- برنامه خود را اجرا کنید و بازی را انجام دهید. برخی یا همه کلمات را دور بزنید و روی پایان بازی ضربه بزنید. توجه داشته باشید که بخش امتیاز اکنون امتیاز نهایی را نشان می دهد.

- اختیاری: گزارش های
ScoreViewModelرا در Logcat با فیلتر کردن درScoreViewModelبررسی کنید. مقدار امتیاز باید نمایش داده شود.
2019-02-07 10:50:18.328 com.example.android.guesstheword I/ScoreViewModel: Final score is 15
در این کار، ScoreFragment برای استفاده ViewModel پیاده سازی کردید. همچنین یاد گرفتید که چگونه با استفاده از رابط ViewModelFactory یک سازنده پارامتری برای ViewModel ایجاد کنید.
تبریک می گویم! شما معماری برنامه خود را برای استفاده از یکی از اجزای معماری Android، ViewModel تغییر دادید. شما مشکل چرخه عمر برنامه را حل کردید و اکنون داده های بازی از تغییرات پیکربندی جان سالم به در می برند. همچنین یاد گرفتید که چگونه با استفاده از رابط ViewModelFactory یک سازنده پارامتری برای ایجاد ViewModel ایجاد کنید.
پروژه اندروید استودیو: GuessTheWord
- دستورالعملهای معماری برنامه اندروید جداسازی کلاسهایی را که مسئولیتهای متفاوتی دارند توصیه میکند.
- یک کنترلر UI کلاس مبتنی بر UI مانند
ActivityیاFragmentاست. کنترلکنندههای UI فقط باید دارای منطقی باشند که تعاملات UI و سیستم عامل را مدیریت میکند. آنها نباید حاوی داده هایی برای نمایش در UI باشند. آن داده ها را درViewModelقرار دهید. - کلاس
ViewModelداده های مربوط به UI را ذخیره و مدیریت می کند. کلاسViewModelبه داده ها اجازه می دهد تا از تغییرات پیکربندی مانند چرخش صفحه زنده بمانند. -
ViewModelیکی از مؤلفههای معماری اندروید توصیه شده است. -
ViewModelProvider.Factoryیک رابط است که می توانید از آن برای ایجاد یک شیViewModelاستفاده کنید.
جدول زیر کنترلکنندههای UI را با نمونههای ViewModel که دادهها را برای آنها نگه میدارد مقایسه میکند:
کنترلر رابط کاربری | ViewModel |
نمونه ای از یک کنترلر UI | نمونه ای از |
هیچ داده ای برای نمایش در رابط کاربری ندارد. | حاوی داده هایی است که کنترلر رابط کاربری در رابط کاربری نمایش می دهد. |
حاوی کدی برای نمایش داده ها و کد رویداد کاربر مانند شنوندگان کلیک است. | حاوی کد برای پردازش داده ها |
در طول هر تغییر پیکربندی، از بین می رود و دوباره ایجاد می شود. | تنها زمانی از بین میرود که کنترلکننده رابط کاربری مرتبط برای همیشه از بین برود - برای یک فعالیت، زمانی که فعالیت تمام میشود، یا برای یک قطعه، وقتی قطعه جدا میشود. |
شامل نماها | هرگز نباید حاوی ارجاعاتی به فعالیت ها، قطعات یا نماها باشد، زیرا آنها از تغییرات پیکربندی جان سالم به در نمی برند، اما |
حاوی ارجاع به | هیچ ارجاعی به کنترلر رابط کاربری مربوطه ندارد. |
دوره بی ادبی:
مستندات توسعه دهنده اندروید:
- نمای کلی مدل ViewModel
- مدیریت چرخه زندگی با اجزای مربوط به چرخه حیات
- راهنمای معماری اپلیکیشن
-
ViewModelProvider -
ViewModelProvider.Factory
دیگر:
- الگوی معماری MVVM (model-view-viewmodel).
- اصل طراحی جداسازی نگرانی ها (SoC).
- الگوی روش کارخانه
این بخش، تکالیف احتمالی را برای دانشآموزانی که در این آزمایشگاه کد به عنوان بخشی از دورهای که توسط یک مربی هدایت میشود، کار میکنند، فهرست میکند. این وظیفه مربی است که موارد زیر را انجام دهد:
- در صورت نیاز تکالیف را تعیین کنید.
- نحوه ارسال تکالیف را با دانش آموزان در میان بگذارید.
- تکالیف را نمره دهید.
مربیان میتوانند از این پیشنهادات به اندازهای که میخواهند استفاده کنند، و باید با خیال راحت هر تکلیف دیگری را که فکر میکنند مناسب است، محول کنند.
اگر به تنهایی از طریق این کدها کار می کنید، از این تکالیف برای آزمایش دانش خود استفاده کنید.
به این سوالات پاسخ دهید
سوال 1
برای جلوگیری از از دست رفتن داده ها در طول تغییر پیکربندی دستگاه، باید داده های برنامه را در کدام کلاس ذخیره کنید؟
-
ViewModel -
LiveData -
Fragment -
Activity
سوال 2
یک ViewModel هرگز نباید حاوی ارجاعی به قطعات، فعالیت ها یا نماها باشد. درست یا غلط؟
- درست است
- نادرست
سوال 3
چه زمانی یک ViewModel از بین می رود؟
- هنگامی که کنترلر رابط کاربری مرتبط با تغییر جهت گیری دستگاه از بین می رود و دوباره ایجاد می شود.
- در یک تغییر جهت.
- وقتی کنترلر UI مرتبط تمام شد (اگر یک فعالیت باشد) یا جدا شد (اگر قطعه باشد).
- هنگامی که کاربر دکمه بازگشت را فشار می دهد.
سوال 4
رابط ViewModelFactory برای چیست؟
- نمونه سازی یک شی
ViewModel. - حفظ داده ها در طول تغییر جهت.
- تازه کردن داده هایی که روی صفحه نمایش داده می شود.
- دریافت اعلان ها هنگام تغییر داده های برنامه.
درس بعدی را شروع کنید:
برای پیوند به سایر کدهای این دوره، به صفحه فرود کد لبههای کد پایه Android Kotlin Fundamentals مراجعه کنید.




