Android Kotlin Fundamentals 06.2: Coroutines and Room

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

مقدمه

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

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

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

باید با:

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

کد شروع برای این Codelab همانند کد راه حل برای 6.1 Create a Room database codelab است.

  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 به برنامه‌ای که این قطعه به آن متصل است، نیاز دارید.

    اگر مقدار null باشد، تابع requireNotNull Kotlin یک IllegalArgumentException ایجاد می کند.
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 به شما امکان می دهند کد مبتنی بر تماس را به کد ترتیبی تبدیل کنید. کدهایی که به صورت متوالی نوشته می‌شوند معمولاً خواندن آسان‌تر هستند و حتی می‌توانند از ویژگی‌های زبانی مانند استثناها استفاده کنند. در پایان، کوروتین‌ها و فراخوان‌ها همین کار را انجام می‌دهند: منتظر می‌مانند تا نتیجه‌ای از یک کار طولانی‌مدت در دسترس باشد و به اجرا ادامه می‌دهند.

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

محدوده: محدوده یک کوروتین زمینه ای را که در آن کوروتین اجرا می شود، تعریف می کند. یک محدوده اطلاعاتی در مورد شغل و توزیع کننده یک کوروتین ترکیب می کند. 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

تابع suspend 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 مراجعه کنید.