Android Kotlin Fundamentals 06.2: Coroutines and Room

این کد لبه بخشی از دوره آموزشی Android Kotlin Fundamentals است. اگر به ترتیب روی کدها کار کنید، بیشترین ارزش را از این دوره خواهید گرفت. همه کد لبه های دوره در صفحه فرود کد لبه های Android Kotlin Fundamentals فهرست شده اند.

مقدمه

یکی از اولویت های اصلی برای ایجاد یک تجربه کاربری بی عیب و نقص برای برنامه شما این است که مطمئن شوید رابط کاربری همیشه پاسخگو بوده و به خوبی اجرا می شود. یکی از راه‌های بهبود عملکرد رابط کاربری، انتقال وظایف طولانی‌مدت، مانند عملیات پایگاه داده، به پس‌زمینه است.

در این نرم افزار کد، بخش رو به روی کاربر از برنامه TrackMySleepQuality را با استفاده از کوروتین های Kotlin برای انجام عملیات پایگاه داده به دور از رشته اصلی پیاده سازی می کنید.

آنچه از قبل باید بدانید

باید با:

  • ساخت یک رابط کاربری اولیه (UI) با استفاده از یک اکتیویتی، قطعات، نماها و کنترل کننده های کلیک.
  • پیمایش بین قطعات و استفاده از safeArgs برای انتقال داده های ساده بین قطعات.
  • مشاهده مدل‌ها، مشاهده کارخانه‌های مدل، تبدیل‌ها و LiveData .
  • چگونه یک پایگاه داده Room ایجاد کنیم، یک DAO ایجاد کنیم و موجودیت ها را تعریف کنیم.
  • اگر با مفاهیم threading و multiprocessing آشنایی دارید مفید است.

چیزی که یاد خواهید گرفت

  • نحوه کار رشته ها در اندروید
  • نحوه استفاده از کوروتین های Kotlin برای دور کردن عملیات پایگاه داده از موضوع اصلی.
  • نحوه نمایش داده های فرمت شده در TextView .

کاری که خواهی کرد

  • برنامه TrackMySleepQuality را برای جمع آوری، ذخیره و نمایش داده ها در داخل و از پایگاه داده گسترش دهید.
  • از کوروتین ها برای اجرای عملیات طولانی مدت پایگاه داده در پس زمینه استفاده کنید.
  • از LiveData برای راه اندازی ناوبری و نمایش نوار اسنک استفاده کنید.
  • از LiveData برای فعال و غیرفعال کردن دکمه ها استفاده کنید.

در این کد لبه، شما مدل view، برنامه‌های روتین و بخش‌های نمایش داده برنامه TrackMySleepQuality را می‌سازید.

این برنامه دارای دو صفحه است که با قطعات نشان داده شده است، همانطور که در شکل زیر نشان داده شده است.

اولین صفحه نمایش داده شده در سمت چپ دارای دکمه هایی برای شروع و توقف ردیابی است. صفحه نمایش تمام داده های خواب کاربر را نشان می دهد. دکمه Clear تمام داده هایی را که برنامه برای کاربر جمع آوری کرده است برای همیشه حذف می کند.

صفحه دوم، نشان داده شده در سمت راست، برای انتخاب رتبه بندی کیفیت خواب است. در برنامه، امتیاز به صورت عددی نشان داده می شود. برای اهداف توسعه، برنامه هم نمادهای چهره و هم معادل های عددی آنها را نشان می دهد.

جریان کاربر به شرح زیر است:

  • کاربر برنامه را باز می کند و با صفحه ردیابی خواب نمایش داده می شود.
  • کاربر روی دکمه Start ضربه می زند. این زمان شروع را ثبت کرده و نمایش می دهد. دکمه Start غیرفعال است و دکمه Stop فعال است.
  • کاربر روی دکمه Stop ضربه می زند. این کار زمان پایان را ثبت می کند و صفحه با کیفیت خواب را باز می کند.
  • کاربر یک نماد کیفیت خواب را انتخاب می کند. صفحه بسته می شود و صفحه ردیابی زمان پایان خواب و کیفیت خواب را نشان می دهد. دکمه Stop غیرفعال و دکمه Start فعال است. برنامه برای یک شب دیگر آماده است.
  • دکمه Clear هر زمان که داده ای در پایگاه داده وجود داشته باشد فعال می شود. هنگامی که کاربر روی دکمه Clear ضربه می‌زند، تمام داده‌های او بدون مراجعه پاک می‌شوند - "آیا مطمئن هستید؟" وجود ندارد. پیام

این برنامه از معماری ساده شده استفاده می کند، همانطور که در زیر در زمینه معماری کامل نشان داده شده است. این برنامه فقط از اجزای زیر استفاده می کند:

  • کنترلر رابط کاربری
  • مشاهده مدل و LiveData
  • پایگاه داده اتاق

در این کار، از TextView برای نمایش داده های ردیابی خواب قالب بندی شده استفاده می کنید. (این رابط نهایی نیست. شما راه بهتری را در یک Codelab دیگر یاد خواهید گرفت.)

می‌توانید با برنامه TrackMySleepQuality که در آزمایشگاه کد قبلی ساخته‌اید ادامه دهید یا برنامه شروع را برای این کد لبه دانلود کنید.

مرحله 1: برنامه شروع را دانلود و اجرا کنید

  1. برنامه TrackMySleepQuality-Coroutines-Starter را از GitHub دانلود کنید.
  2. برنامه را بسازید و اجرا کنید. برنامه رابط کاربری بخش SleepTrackerFragment را نشان می دهد، اما داده ای ندارد. دکمه ها به ضربه ها پاسخ نمی دهند.

مرحله 2: کد را بررسی کنید

کد شروع برای این کد لبه با کد راه حل 6.1 Create a Room Datalab Code یکسان است.

  1. Res/layout/activity_main.xml را باز کنید. این طرح شامل قطعه nav_host_fragment است. همچنین به تگ <merge> توجه کنید.

    تگ merge می تواند برای حذف طرح بندی های اضافی هنگام گنجاندن طرح بندی استفاده شود و استفاده از آن ایده خوبی است. نمونه ای از یک طرح اضافی می تواند ConstraintLayout > LinearLayout > TextView باشد، جایی که سیستم ممکن است بتواند LinearLayout را حذف کند. این نوع بهینه سازی می تواند سلسله مراتب نمایش را ساده کرده و عملکرد برنامه را بهبود بخشد.
  2. در پوشه پیمایش ، navigation.xml را باز کنید. شما می توانید دو قطعه و اقدامات ناوبری را که آنها را به هم متصل می کنند، مشاهده کنید.
  3. در پوشه layout ، روی قطعه ردیاب خواب دوبار کلیک کنید تا طرح XML آن را ببینید. به موارد زیر توجه کنید:
  • داده های طرح بندی در یک عنصر <layout> پیچیده شده است تا اتصال داده ها را فعال کند.
  • ConstraintLayout و سایر نماها در داخل عنصر <layout> مرتب شده اند.
  • این فایل دارای یک برچسب <data> است.

برنامه شروع همچنین ابعاد، رنگ ها و استایل را برای رابط کاربری ارائه می دهد. این برنامه شامل یک پایگاه داده Room ، یک DAO و یک موجودیت SleepNight است. اگر نسخه کد قبلی را کامل نکرده اید، مطمئن شوید که این جنبه های کد را به تنهایی بررسی کرده اید.

اکنون که یک پایگاه داده و یک رابط کاربری دارید، باید داده ها را جمع آوری کنید، داده ها را به پایگاه داده اضافه کنید و داده ها را نمایش دهید. تمام این کارها در مدل view انجام می شود. مدل نمای ردیاب خواب شما با کلیک روی دکمه کنترل می‌شود، از طریق DAO با پایگاه داده تعامل می‌کند و داده‌ها را از طریق LiveData به رابط کاربری ارائه می‌کند. تمام عملیات پایگاه داده باید از رشته UI اصلی خارج شوند و شما این کار را با استفاده از برنامه های مشترک انجام خواهید داد.

مرحله 1: SleepTrackerViewModel را اضافه کنید

  1. در بسته Sleeptracker ، SleepTrackerViewModel.kt را باز کنید.
  2. کلاس SleepTrackerViewModel را که در برنامه استارتر برای شما ارائه شده و در زیر نیز نشان داده شده است را بررسی کنید. توجه داشته باشید که کلاس AndroidViewModel() را گسترش می دهد. این کلاس مانند ViewModel است، اما زمینه برنامه را به عنوان پارامتر می گیرد و آن را به عنوان یک ویژگی در دسترس قرار می دهد. بعدا به این نیاز خواهید داشت.
class SleepTrackerViewModel(
       val database: SleepDatabaseDao,
       application: Application) : AndroidViewModel(application) {
}

مرحله 2: SleepTrackerViewModelFactory را اضافه کنید

  1. در بسته Sleeptracker ، SleepTrackerViewModelFactory.kt را باز کنید.
  2. کدی که برای کارخانه برای شما ارائه شده است را بررسی کنید که در زیر نشان داده شده است:
class SleepTrackerViewModelFactory(
       private val dataSource: SleepDatabaseDao,
       private val application: Application) : ViewModelProvider.Factory {
   @Suppress("unchecked_cast")
   override fun <T : ViewModel?> create(modelClass: Class<T>): T {
       if (modelClass.isAssignableFrom(SleepTrackerViewModel::class.java)) {
           return SleepTrackerViewModel(dataSource, application) as T
       }
       throw IllegalArgumentException("Unknown ViewModel class")
   }
}

به موارد زیر توجه کنید:

  • SleepTrackerViewModelFactory ارائه شده همان آرگومان ViewModel را می گیرد و ViewModelProvider.Factory را گسترش می دهد.
  • در داخل کارخانه، کد create() را لغو می کند، که هر نوع کلاسی را به عنوان آرگومان می گیرد و ViewModel برمی گرداند.
  • در بدنه create() کد بررسی می کند که یک کلاس SleepTrackerViewModel در دسترس است و اگر وجود داشته باشد، نمونه ای از آن را برمی گرداند. در غیر این صورت، کد یک استثنا ایجاد می کند.

مرحله 3: SleepTrackerFragment را به روز کنید

  1. در SleepTrackerFragment ، یک مرجع به زمینه برنامه دریافت کنید. مرجع را در onCreateView() زیر binding قرار دهید. برای ارسال به ارائه‌دهنده کارخانه view-model، به برنامه‌ای که این قطعه به آن متصل است، نیاز دارید.

    تابع requireNotNull Kotlin یک IllegalArgumentException را در صورت null بودن مقدار ایجاد می کند.
val application = requireNotNull(this.activity).application
  1. شما نیاز به ارجاع به منبع داده خود از طریق ارجاع به DAO دارید. در onCreateView() قبل از return ، یک dataSource تعریف کنید. برای دریافت ارجاع به DAO پایگاه داده، از SleepDatabase.getInstance(application).sleepDatabaseDao استفاده کنید.
val dataSource = SleepDatabase.getInstance(application).sleepDatabaseDao
  1. در onCreateView() ، قبل از return ، یک نمونه از viewModelFactory ایجاد کنید. باید آن را dataSource و application ارسال کنید.
val viewModelFactory = SleepTrackerViewModelFactory(dataSource, application)
  1. اکنون که یک کارخانه دارید، یک مرجع به SleepTrackerViewModel دریافت کنید. پارامتر SleepTrackerViewModel::class.java به کلاس جاوا زمان اجرا این شی اشاره دارد.
val sleepTrackerViewModel =
       ViewModelProviders.of(
               this, viewModelFactory).get(SleepTrackerViewModel::class.java)
  1. کد تمام شده شما باید به شکل زیر باشد:
// Create an instance of the ViewModel Factory.
val dataSource = SleepDatabase.getInstance(application).sleepDatabaseDao
val viewModelFactory = SleepTrackerViewModelFactory(dataSource, application)

// Get a reference to the ViewModel associated with this fragment.
val sleepTrackerViewModel =
       ViewModelProviders.of(
               this, viewModelFactory).get(SleepTrackerViewModel::class.java)

در اینجا روش onCreateView() تاکنون آمده است:

override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
                              savedInstanceState: Bundle?): View? {

        // Get a reference to the binding object and inflate the fragment views.
        val binding: FragmentSleepTrackerBinding = DataBindingUtil.inflate(
                inflater, R.layout.fragment_sleep_tracker, container, false)

        val application = requireNotNull(this.activity).application

        val dataSource = SleepDatabase.getInstance(application).sleepDatabaseDao

        val viewModelFactory = SleepTrackerViewModelFactory(dataSource, application)

        val sleepTrackerViewModel =
                ViewModelProviders.of(
                        this, viewModelFactory).get(SleepTrackerViewModel::class.java)

        return binding.root
    }

مرحله 4: پیوند داده را برای مدل view اضافه کنید

با نصب ViewModel اصلی، برای اتصال ViewModel به UI، باید تنظیمات اتصال داده را در SleepTrackerFragment تمام کنید.


در فایل طرح بندی fragment_sleep_tracker.xml :

  1. در داخل بلوک <data> ، یک <variable> ایجاد کنید که به کلاس SleepTrackerViewModel ارجاع می دهد.
<data>
   <variable
       name="sleepTrackerViewModel"
       type="com.example.android.trackmysleepquality.sleeptracker.SleepTrackerViewModel" />
</data>

در SleepTrackerFragment :

  1. فعالیت فعلی را به عنوان مالک چرخه حیات اتصال تنظیم کنید. این کد را در متد onCreateView() قبل از دستور return اضافه کنید:
binding.setLifecycleOwner(this)
  1. متغیر binding sleepTrackerViewModel را به sleepTrackerViewModel اختصاص دهید. این کد را داخل onCreateView() زیر کدی که SleepTrackerViewModel را ایجاد می کند قرار دهید:
binding.sleepTrackerViewModel = sleepTrackerViewModel
  1. احتمالاً خطا می بینید، زیرا باید شی binding را دوباره ایجاد کنید. پروژه را تمیز و بازسازی کنید تا از خطا خلاص شوید.
  2. در نهایت، مثل همیشه، مطمئن شوید که کد شما بدون خطا ساخته و اجرا می شود.

در کاتلین، کوروتین ها راهی برای انجام وظایف طولانی مدت به زیبایی و کارآمدی هستند. کوروتین های Kotlin به شما امکان می دهند کد مبتنی بر تماس را به کد ترتیبی تبدیل کنید. کدهایی که به صورت متوالی نوشته می شوند معمولاً خواندن آسان تر است و حتی می توانند از ویژگی های زبان مانند استثناها استفاده کنند. در پایان، کوروتین‌ها و کال‌بک‌ها همین کار را انجام می‌دهند: آن‌ها منتظر می‌مانند تا نتیجه‌ای از یک کار طولانی‌مدت در دسترس باشد و به اجرا ادامه می‌دهند.

کوروتین ها دارای ویژگی های زیر هستند:

  • کوروتین ها ناهمزمان و غیر مسدود هستند.
  • Coroutineها از توابع تعلیق برای ایجاد کدهای ناهمزمان به ترتیب استفاده می کنند.

کوروتین ها ناهمزمان هستند.

یک کوروتین مستقل از مراحل اصلی اجرای برنامه شما اجرا می شود. این می تواند به صورت موازی یا روی یک پردازنده جداگانه باشد. همچنین ممکن است در حالی که بقیه برنامه منتظر ورودی هستند، شما به صورت مخفیانه در پردازش کمی قرار بگیرید. یکی از جنبه های مهم async این است که نمی توانید انتظار داشته باشید که نتیجه در دسترس باشد، مگر اینکه صریحاً منتظر آن باشید.

مثلاً فرض کنید سؤالی دارید که نیاز به تحقیق دارد و از یک همکار می‌خواهید که پاسخ آن را بیابد. آنها می روند و روی آن کار می کنند، که مثل این است که کار را «ناهمزمان» و «روی یک موضوع جداگانه» انجام می دهند. تا زمانی که همکارتان برگردد و به شما بگوید پاسخ چیست، می‌توانید کارهای دیگری را که به پاسخ بستگی ندارد ادامه دهید.

کوروتین ها غیر مسدود کننده هستند.

غیر مسدود کردن به این معنی است که یک کوروتین رشته اصلی یا رابط کاربری را مسدود نمی کند. بنابراین با استفاده از برنامه‌ها، کاربران همیشه روان‌ترین تجربه ممکن را دارند، زیرا تعامل رابط کاربری همیشه اولویت دارد.

Coroutineها از توابع تعلیق برای ایجاد کدهای ناهمزمان به ترتیب استفاده می کنند.

کلمه کلیدی suspend روشی است که کاتلین برای علامت گذاری یک تابع یا نوع تابع به عنوان در دسترس بودن در روال ها است. هنگامی که یک کوروتین تابعی را که با suspend مشخص شده است فراخوانی می کند، به جای مسدود کردن تا زمانی که تابع مانند یک فراخوانی تابع معمولی بازگردد، کوروتین اجرا را تا زمانی که نتیجه آماده شود به حالت تعلیق در می آورد. سپس کار روتین از همان جایی که متوقف شد، با نتیجه از سر گرفته می شود.

در حالی که کوروتین به حالت تعلیق درآمده و منتظر نتیجه است، رشته‌ای را که روی آن اجرا می‌شود، مسدود می‌کند. به این ترتیب، سایر توابع یا برنامه‌ها می‌توانند اجرا شوند.

کلمه کلیدی suspend رشته ای که کد روی آن اجرا می شود را مشخص نمی کند. یک تابع تعلیق ممکن است روی یک رشته پس‌زمینه یا روی رشته اصلی اجرا شود.

برای استفاده از کوروتین ها در کاتلین، به سه چیز نیاز دارید:

  • یک شغل
  • یک اعزام کننده
  • یک محدوده

شغل : اساساً شغل هر چیزی است که بتوان آن را لغو کرد. هر کوروتین کاری دارد و شما می توانید از آن کار برای لغو کوروتین استفاده کنید. مشاغل را می توان در سلسله مراتب والدین-فرزند مرتب کرد. لغو یک شغل والدین بلافاصله همه فرزندان کار را لغو می کند، که بسیار راحت تر از لغو هر یک از کارهای روزمره به صورت دستی است.

Dispatcher: Dispatcher برنامه هایی را برای اجرا در موضوعات مختلف ارسال می کند. به عنوان مثال، Dispatcher.Main وظایف را در رشته اصلی اجرا می کند، و Dispatcher.IO مسدود کردن وظایف I/O را به یک مجموعه مشترک از رشته ها تخلیه می کند.

محدوده: محدوده یک کوروتین زمینه ای را که در آن کوروتین اجرا می شود، تعریف می کند. یک محدوده اطلاعات مربوط به کار و توزیع کننده یک کوروتین را ترکیب می کند. Scope ها برنامه های کاری را پیگیری می کنند. هنگامی که یک کوروتین را راه‌اندازی می‌کنید، "در یک محدوده" است، به این معنی که شما مشخص کرده‌اید که کدام محدوده آن را دنبال می‌کند.

شما می خواهید که کاربر بتواند با داده های خواب به روش های زیر تعامل داشته باشد:

  • هنگامی که کاربر روی دکمه Start ضربه می زند، برنامه یک شب خواب جدید ایجاد می کند و شب خواب را در پایگاه داده ذخیره می کند.
  • وقتی کاربر روی دکمه توقف ضربه می‌زند، برنامه شب را با زمان پایان به‌روزرسانی می‌کند.
  • وقتی کاربر روی دکمه Clear ضربه می‌زند، برنامه داده‌های پایگاه داده را پاک می‌کند.

این عملیات پایگاه داده می تواند زمان زیادی طول بکشد، بنابراین باید در یک رشته جداگانه اجرا شوند.

مرحله 1: کوروتین ها را برای عملیات پایگاه داده تنظیم کنید

وقتی روی دکمه Start در برنامه Sleep Tracker ضربه می‌زنید، می‌خواهید تابعی را در SleepTrackerViewModel فراخوانی کنید تا یک نمونه جدید از SleepNight ایجاد کنید و نمونه را در پایگاه داده ذخیره کنید.

با ضربه زدن روی هر یک از دکمه‌ها، عملیات پایگاه داده مانند ایجاد یا به‌روزرسانی SleepNight فعال می‌شود. به همین دلیل و موارد دیگر، شما از کوروتین ها برای پیاده سازی کنترل کننده های کلیک برای دکمه های برنامه استفاده می کنید.

  1. فایل build.gradle در سطح برنامه را باز کنید و وابستگی های کوروتین ها را پیدا کنید. برای استفاده از کوروتین ها به این وابستگی ها نیاز دارید که برای شما اضافه شده است.

    $coroutine_version در فایل build.gradle پروژه به صورت coroutine_version = '1.0.0' تعریف شده است .
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:$coroutine_version"
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$coroutine_version"
  1. فایل SleepTrackerViewModel را باز کنید.
  2. در بدنه کلاس، viewModelJob تعریف کرده و یک نمونه از Job به آن اختصاص دهید. این viewModelJob به شما این امکان را می‌دهد تا زمانی که مدل view دیگر استفاده نمی‌شود و از بین می‌رود، تمام برنامه‌هایی که توسط این مدل view شروع شده‌اند را لغو کنید. به این ترتیب، شما در نهایت با کوروتین هایی مواجه نمی شوید که جایی برای بازگشت ندارند.
private var viewModelJob = Job()
  1. در پایان بدنه کلاس، onCleared() را لغو کنید و همه برنامه‌ها را لغو کنید. هنگامی که ViewModel از بین می رود، onCleared() فراخوانی می شود.
override fun onCleared() {
   super.onCleared()
   viewModelJob.cancel()
}
  1. درست در زیر تعریف viewModelJob ، یک uiScope برای کوروتین ها تعریف کنید. scope تعیین می‌کند که کوروتین روی چه رشته‌ای اجرا می‌شود، و دامنه نیز باید درباره کار بداند. برای دریافت یک محدوده، یک نمونه از CoroutineScope را بخواهید و در یک توزیع کننده و یک شغل عبور کنید.

استفاده از Dispatchers.Main به این معنی است که روال‌های راه‌اندازی شده در uiScope روی رشته اصلی اجرا می‌شوند. این برای بسیاری از برنامه‌های معمولی که توسط ViewModel شروع شده‌اند، معقول است، زیرا پس از انجام برخی پردازش‌ها، منجر به به‌روزرسانی رابط کاربری می‌شوند.

private val uiScope = CoroutineScope(Dispatchers.Main + viewModelJob)
  1. در زیر تعریف uiScope ، یک متغیر به نام tonight برای نگه داشتن شب جاری تعریف کنید. متغیر MutableLiveData بسازید، زیرا باید بتوانید داده ها را مشاهده کرده و تغییر دهید.
private var tonight = MutableLiveData<SleepNight?>()
  1. برای مقداردهی اولیه متغیر tonight در اسرع وقت، یک بلوک init زیر تعریف tonight ایجاد کنید و initializeTonight() را فراخوانی کنید. در مرحله بعد initializeTonight() تعریف می کنید.
init {
   initializeTonight()
}
  1. در زیر بلوک init ، initializeTonight() را پیاده سازی کنید. در uiScope ، یک coroutine را راه اندازی کنید. در داخل، با فراخوانی getTonightFromDatabase() مقدار tonight را از پایگاه داده دریافت کنید و مقدار را به tonight.value اختصاص دهید. در مرحله بعد getTonightFromDatabase() تعریف می کنید.
private fun initializeTonight() {
   uiScope.launch {
       tonight.value = getTonightFromDatabase()
   }
}
  1. پیاده سازی getTonightFromDatabase() . آن را به عنوان یک تابع private suspend تعریف کنید که یک SleepNight قابل تهی را برمی گرداند، اگر جریانی وجود نداشته باشد که SleepNight شروع شده باشد. این شما را با یک خطا مواجه می کند، زیرا تابع باید چیزی را برگرداند.
private suspend fun getTonightFromDatabase(): SleepNight? { }
  1. در داخل بدنه تابع getTonightFromDatabase() ، نتیجه را از یک کوروتین که در زمینه Dispatchers.IO اجرا می شود، برگردانید. از دیسپچر I/O استفاده کنید، زیرا دریافت داده از پایگاه داده یک عملیات I/O است و ربطی به رابط کاربری ندارد.
  return withContext(Dispatchers.IO) {}
  1. در داخل بلوک برگشتی، اجازه دهید کوروتین امشب (جدیدترین شب) از پایگاه داده دریافت شود. اگر زمان شروع و پایان یکسان نیست، به این معنی که شب قبلاً کامل شده است، null را برگردانید. در غیر این صورت شب را برگردانید.
       var night = database.getTonight()
       if (night?.endTimeMilli != night?.startTimeMilli) {
           night = null
       }
       night

تابع تعلیق getTonightFromDatabase() شما باید شبیه این باشد. دیگر نباید خطا داشته باشد.

private suspend fun getTonightFromDatabase(): SleepNight? {
   return withContext(Dispatchers.IO) {
       var night = database.getTonight()
       if (night?.endTimeMilli != night?.startTimeMilli) {
           night = null
       }
       night
   }
}

مرحله 2: کنترل کننده کلیک را برای دکمه Start اضافه کنید

اکنون می توانید onStartTracking() پیاده سازی کنید، کنترل کننده کلیک برای دکمه Start . شما باید یک SleepNight جدید ایجاد کنید، آن را در پایگاه داده وارد کنید و آن را به tonight اختصاص دهید. ساختار onStartTracking() بسیار شبیه initializeTonight() خواهد بود.

  1. با تعریف تابع برای onStartTracking() شروع کنید. می‌توانید کنترل‌کننده‌های کلیک را در بالای onCleared() در فایل SleepTrackerViewModel قرار دهید.
fun onStartTracking() {}
  1. در داخل onStartTracking() یک coroutine در uiScope راه اندازی کنید، زیرا برای ادامه و به روز رسانی UI به این نتیجه نیاز دارید.
uiScope.launch {}
  1. در داخل راه‌اندازی معمولی، یک SleepNight جدید ایجاد کنید که زمان فعلی را به عنوان زمان شروع ثبت می‌کند.
        val newNight = SleepNight()
  1. هنوز در راه اندازی کوروتین، insert() برای درج newNight در پایگاه داده فراخوانی کنید. یک خطا خواهید دید، زیرا هنوز این تابع تعلیق insert() را تعریف نکرده اید. (این تابع DAO با همین نام نیست.)
       insert(newNight)
  1. همچنین در راه‌اندازی معمول، tonight به‌روزرسانی کنید.
       tonight.value = getTonightFromDatabase()
  1. در زیر onStartTracking() ، insert() به عنوان یک تابع private suspend تعریف کنید که یک SleepNight به عنوان آرگومان خود می گیرد.
private suspend fun insert(night: SleepNight) {}
  1. برای متن insert() یک coroutine را در زمینه I/O اجرا کنید و با فراخوانی insert() از DAO، شب را در پایگاه داده قرار دهید.
   withContext(Dispatchers.IO) {
       database.insert(night)
   }
  1. در فایل طرح بندی fragment_sleep_tracker.xml ، کنترل کننده کلیک برای onStartTracking() را به start_button با استفاده از جادوی اتصال داده که قبلاً تنظیم کردید اضافه کنید. نماد تابع @{() -> یک تابع لامبدا ایجاد می کند که هیچ آرگومان نمی گیرد و کنترل کننده کلیک را در sleepTrackerViewModel فراخوانی می کند.
android:onClick="@{() -> sleepTrackerViewModel.onStartTracking()}"
  1. اپلیکیشن خود را بسازید و اجرا کنید. روی دکمه Start ضربه بزنید. این عمل داده ایجاد می کند، اما شما هنوز چیزی را نمی توانید ببینید. بعدش شما درستش کنید
fun someWorkNeedsToBeDone {
   uiScope.launch {

        suspendFunction()

   }
}

suspend fun suspendFunction() {
   withContext(Dispatchers.IO) {
       longrunningWork()
   }
}

مرحله 3: نمایش داده ها

در SleepTrackerViewModel ، متغیر nights به LiveData اشاره می کند زیرا getAllNights() در DAO LiveData برمی گرداند.

این یک ویژگی Room است که هر بار که داده‌های پایگاه داده تغییر می‌کند، nights LiveData برای نمایش آخرین داده‌ها به روز می‌شود. شما هرگز نیازی به تنظیم صریح LiveData یا به روز رسانی آن ندارید. Room داده ها را برای مطابقت با پایگاه داده به روز می کند.

با این حال، اگر nights در نمای متنی نمایش دهید، مرجع شی را نشان می دهد. برای دیدن محتویات شی، داده ها را به یک رشته فرمت شده تبدیل کنید. از یک نقشه Transformation استفاده کنید که هر بار که nights داده های جدیدی از پایگاه داده دریافت می کنند اجرا می شود.

  1. فایل Util.kt را باز کنید و کد مربوط به تعریف formatNights() و دستورات import مرتبط را از حالت کامنت خارج کنید. برای لغو کامنت کد در Android Studio، تمام کدهایی که با // مشخص شده اند را انتخاب کنید و Cmd+/ یا Control+/ را فشار دهید.
  2. توجه داشته باشید که formatNights() یک نوع Spanned را برمی گرداند که یک رشته با فرمت HTML است.
  3. strings.xml را باز کنید. به استفاده از CDATA برای قالب بندی منابع رشته ای برای نمایش داده های خواب توجه کنید.
  4. SleepTrackerViewModel را باز کنید. در کلاس SleepTrackerViewModel ، در زیر تعریف uiScope ، متغیری به نام nights تعریف کنید. تمام شب ها را از پایگاه داده دریافت کنید و آنها را به متغیر nights اختصاص دهید.
private val nights = database.getAllNights()
  1. درست در زیر تعریف nights ، کدی را برای تبدیل nights به nightsString اضافه کنید. از تابع formatNights() از Util.kt استفاده کنید.

    nights به تابع map() از کلاس Transformations منتقل کنید. برای دسترسی به منابع رشته خود، تابع نگاشت را به صورت فراخوانی formatNights() تعریف کنید. nights عرضه و یک شی Resources .
val nightsString = Transformations.map(nights) { nights ->
   formatNights(nights, application.resources)
}
  1. فایل طرح بندی fragment_sleep_tracker.xml را باز کنید. در TextView ، در ویژگی android:text ، اکنون می‌توانید رشته منبع را با ارجاع به nightsString جایگزین کنید.
"@{sleepTrackerViewModel.nightsString}"
  1. کد خود را دوباره بسازید و برنامه را اجرا کنید. همه داده های خواب شما با زمان شروع باید اکنون نمایش داده شود.
  2. چند بار دیگر روی دکمه Start ضربه بزنید و داده های بیشتری را مشاهده خواهید کرد.

در مرحله بعد، عملکرد دکمه Stop را فعال می کنید.

مرحله 4: کنترل کننده کلیک را برای دکمه Stop اضافه کنید

با استفاده از همان الگوی مرحله قبل، کنترل کننده کلیک دکمه Stop را در SleepTrackerViewModel.

  1. onStopTracking() را به ViewModel اضافه کنید. یک coroutine را در uiScope راه اندازی کنید. اگر زمان پایان هنوز تنظیم نشده است، endTimeMilli را روی زمان فعلی سیستم تنظیم کنید و update() با داده های شبانه فراخوانی کنید.

    در Kotlin، نحو label return@ تابعی را که این عبارت از آن برمی‌گرداند، در میان چندین تابع تو در تو، مشخص می‌کند.
fun onStopTracking() {
   uiScope.launch {
       val oldNight = tonight.value ?: return@launch
       oldNight.endTimeMilli = System.currentTimeMillis()
       update(oldNight)
   }
}
  1. با استفاده از همان الگویی که برای پیاده سازی insert() استفاده کردید، update() پیاده سازی کنید.
private suspend fun update(night: SleepNight) {
   withContext(Dispatchers.IO) {
       database.update(night)
   }
}
  1. برای اتصال کنترل کننده کلیک به رابط کاربری، فایل طرح بندی fragment_sleep_tracker.xml را باز کنید و کنترل کننده کلیک را به stop_button اضافه کنید.
android:onClick="@{() -> sleepTrackerViewModel.onStopTracking()}"
  1. اپلیکیشن خود را بسازید و اجرا کنید.
  2. روی Start و سپس Stop ضربه بزنید. شما زمان شروع، زمان پایان، کیفیت خواب بدون ارزش و زمان خواب را مشاهده می کنید.

مرحله 5: کنترل کننده کلیک را برای دکمه Clear اضافه کنید

  1. به طور مشابه، onClear() و clear() را پیاده سازی کنید.
fun onClear() {
   uiScope.launch {
       clear()
       tonight.value = null
   }
}

suspend fun clear() {
   withContext(Dispatchers.IO) {
       database.clear()
   }
}
  1. برای اتصال کنترل کننده کلیک به رابط کاربری، fragment_sleep_tracker.xml را باز کنید و کنترل کننده کلیک را به clear_button اضافه کنید.
android:onClick="@{() -> sleepTrackerViewModel.onClear()}"
  1. اپلیکیشن خود را بسازید و اجرا کنید.
  2. برای خلاص شدن از شر تمام داده ها، روی Clear ضربه بزنید. سپس روی Start و Stop ضربه بزنید تا داده های جدید ایجاد شود.

پروژه Android Studio: TrackMySleepQualityCoroutines

  • از ViewModel ، ViewModelFactory و data binding برای تنظیم معماری UI برای برنامه استفاده کنید.
  • برای اینکه رابط کاربری به خوبی اجرا شود، از کوروتین ها برای کارهای طولانی مدت، مانند تمام عملیات پایگاه داده استفاده کنید.
  • کوروتین ها ناهمزمان و غیر مسدود هستند. آنها از توابع suspend برای ساخت کدهای ناهمزمان به ترتیب استفاده می کنند.
  • هنگامی که یک برنامه مشترک یک تابع مشخص شده با suspend را فراخوانی می کند، به جای مسدود کردن تا زمانی که آن تابع مانند یک فراخوانی تابع عادی برگردد، اجرا را تا زمانی که نتیجه آماده شود به حالت تعلیق در می آورد. سپس با نتیجه از همان جایی که متوقف شد، از سر می گیرد.
  • تفاوت بین مسدود کردن و تعلیق در این است که اگر یک نخ مسدود شود، کار دیگری اتفاق نمی افتد. اگر نخ معلق باشد، تا زمانی که نتیجه در دسترس باشد، کار دیگری انجام می شود.

برای راه اندازی یک کوروتین، به یک شغل، یک توزیع کننده و یک محدوده نیاز دارید:

  • اساساً شغل هر چیزی است که بتوان آن را لغو کرد. هر کوروتین کاری دارد، و شما می توانید از یک کار برای لغو یک کار روتین استفاده کنید.
  • دیسپچر برنامه‌هایی را برای اجرا در رشته‌های مختلف ارسال می‌کند. Dispatcher.Main وظایف را روی رشته اصلی اجرا می‌کند و Dispartcher.IO برای بارگذاری وظایف مسدودکننده ورودی/خروجی در یک مجموعه مشترک از رشته‌ها است.
  • دامنه اطلاعات، از جمله شغل و توزیع کننده را ترکیب می کند تا زمینه ای را که برنامه در آن اجرا می شود، تعریف کند. Scope ها برنامه های کاری را پیگیری می کنند.

برای پیاده سازی کنترل کننده های کلیک که عملیات پایگاه داده را راه اندازی می کنند، از این الگو پیروی کنید:

  1. برنامه‌ای را راه‌اندازی کنید که روی رشته اصلی یا UI اجرا می‌شود، زیرا نتیجه روی UI تأثیر می‌گذارد.
  2. برای انجام کار طولانی مدت، یک تابع تعلیق را فراخوانی کنید تا در زمان انتظار برای نتیجه، رشته رابط کاربری را مسدود نکنید.
  3. کار طولانی مدت هیچ ربطی به رابط کاربری ندارد، بنابراین به زمینه I/O بروید. به این ترتیب، کار می تواند در یک thread pool که بهینه شده است اجرا شود و برای این نوع عملیات کنار گذاشته شود.
  4. سپس تابع پایگاه داده را برای انجام کار فراخوانی کنید.

از نقشه Transformations برای ایجاد یک رشته از یک شی LiveData هر بار که شیء تغییر می کند استفاده کنید.

دوره بی ادبی:

مستندات برنامه نویس اندروید:

سایر اسناد و مقالات:

این بخش، تکالیف احتمالی را برای دانش‌آموزانی که در این آزمایشگاه کد به عنوان بخشی از دوره‌ای که توسط یک مربی هدایت می‌شود، کار می‌کنند، فهرست می‌کند. این وظیفه مربی است که موارد زیر را انجام دهد:

  • در صورت نیاز تکالیف را تعیین کنید.
  • نحوه ارسال تکالیف را با دانش آموزان در میان بگذارید.
  • تکالیف را نمره دهید.

مربیان می‌توانند از این پیشنهادات به اندازه‌ای که می‌خواهند استفاده کنند، و باید با خیال راحت هر تکلیف دیگری را که فکر می‌کنند مناسب است، محول کنند.

اگر به تنهایی از طریق این کدها کار می کنید، از این تکالیف برای آزمایش دانش خود استفاده کنید.

به این سوالات پاسخ دهید

سوال 1

کدام یک از موارد زیر از مزایای کوروتین ها هستند:

  • غیر مسدود کننده هستند
  • آنها به صورت ناهمزمان اجرا می شوند.
  • آنها را می توان روی یک رشته غیر از نخ اصلی اجرا کرد.
  • آنها همیشه باعث می شوند برنامه سریعتر اجرا شود.
  • آنها می توانند از استثناها استفاده کنند.
  • آنها را می توان به صورت کد خطی نوشت و خواند.

سوال 2

تابع تعلیق چیست؟

  • یک تابع معمولی که با کلمه کلیدی suspend حاشیه نویسی شده است.
  • تابعی که می توان آن را درون کوروتین ها فراخوانی کرد.
  • در حالی که یک تابع تعلیق در حال اجرا است، رشته فراخوان به حالت تعلیق در می آید.
  • توابع تعلیق همیشه باید در پس‌زمینه اجرا شوند.

سوال 3

تفاوت بین مسدود کردن و تعلیق یک موضوع چیست؟ تمام موارد واقعی را علامت بزنید.

  • وقتی اجرا مسدود می شود، هیچ کار دیگری را نمی توان روی رشته مسدود شده اجرا کرد.
  • هنگامی که اجرا به حالت تعلیق در می‌آید، نخ می‌تواند کارهای دیگری را انجام دهد در حالی که منتظر است تا کار بارگذاری شده کامل شود.
  • تعلیق کارآمدتر است، زیرا ممکن است رشته‌ها منتظر نباشند و کاری انجام ندهند.
  • خواه مسدود یا تعلیق شده باشد، اجرا همچنان منتظر نتیجه کار قبل از ادامه است.

شروع به درس بعدی: 6.3 از LiveData برای کنترل وضعیت دکمه ها استفاده کنید

برای پیوند به سایر کدهای این دوره، به صفحه فرود کد لبه‌های کد پایه Android Kotlin Fundamentals مراجعه کنید.