این کد لبه بخشی از دوره آموزشی 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: یک مرور کد انجام دهید
- در Android Studio، کد را کاوش کنید تا نحوه عملکرد برنامه را درک کنید.
- مطمئن شوید که به فایل های توضیح داده شده در زیر که بسیار مهم هستند نگاه کنید.
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 فقط باید دارای منطقی باشد که تعاملات رابط کاربری و سیستم عامل مانند نمایش نماها و گرفتن ورودی کاربر را مدیریت کند. منطق تصمیم گیری، مانند منطقی که متن برای نمایش را تعیین می کند، در کنترلر 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
از بین برود، callback 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
در برنامه شروع به شرح زیر است:
- قبل از اینکه
ViewModel
را اضافه کنید:
هنگامی که برنامه از طریق یک تغییر پیکربندی مانند چرخش صفحه انجام می شود، قطعه بازی از بین می رود و دوباره ایجاد می شود. داده ها از بین رفته است. - پس از اینکه
ViewModel
را اضافه کردید و داده های رابط کاربری قطعه بازی را بهViewModel
منتقل کردید:
تمام داده هایی که قطعه باید نمایش دهد اکنونViewModel
است. هنگامی که برنامه از طریق تغییر پیکربندی انجام می شود،ViewModel
زنده می ماند و داده ها حفظ می شوند.
در این کار، داده های رابط کاربری برنامه را به همراه روش های پردازش داده ها به کلاس GameViewModel
می کنید. شما این کار را انجام می دهید تا داده ها در طول تغییرات پیکربندی حفظ شوند.
مرحله 1: فیلدهای داده و پردازش داده ها را به ViewModel منتقل کنید
فیلدهای داده و متدهای زیر را از GameFragment
به GameViewModel
:
- فیلدهای داده
word
،score
وwordList
را جابجا کنید. مطمئن شوید کهword
وscore
private
نیستند.
متغیر binding،GameFragmentBinding
را جابه جا نکنید، زیرا حاوی ارجاعاتی به view ها است. این متغیر برای بزرگ کردن طرح، تنظیم شنوندگان کلیک و نمایش داده ها بر روی صفحه - مسئولیت های قطعه استفاده می شود. -
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(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 اضافه کنید.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
، اندروید استودیو خطای یک عضو انتزاعی اجرا نشده را نشان میدهد. برای رفع خطا، متد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
تغییر دادید. شما مشکل چرخه عمر برنامه را حل کردید و اکنون داده های بازی از تغییرات پیکربندی جان سالم به در می برند. همچنین یاد گرفتید که چگونه یک سازنده پارامتری برای ایجاد ViewModel
با استفاده از رابط ViewModelFactory
ایجاد کنید.
پروژه اندروید استودیو: 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 مراجعه کنید.