این کد لبه بخشی از دوره آموزشی 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
وscore
private
نیستند.
متغیر binding،GameFragmentBinding
جابه جا نکنید، زیرا حاوی ارجاعاتی به نماها است. این متغیر برای افزایش طرح، تنظیم شنوندگان کلیک و نمایش داده ها بر روی صفحه استفاده می شود - مسئولیت های قطعه. - متدهای
resetList()
وnextWord()
را جابه جا کنید. این روش ها تصمیم می گیرند که چه کلمه ای روی صفحه نمایش داده شود. - از داخل متد
onCreateView()
، فراخوانی های متد را بهresetList()
وnextWord()
به بلوکinit
GameViewModel
منتقل کنید.
این متدها باید در بلوکinit
باشند، زیرا باید لیست کلمات را هنگام ایجادViewModel
بازنشانی کنید، نه هر بار که قطعه ایجاد می شود. می توانید عبارت log را در بلوکinit
GameFragment
حذف کنید.
کنترل کننده های کلیک 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 مراجعه کنید.