حسام رسولیان
برنامه نویس موبایل

آشنایی با Flow در زبان برنامه نویسی کاتلین

در بحث کوروتین ها با توابع suspend  آشنا شده ایم, این توابع یک مقدار بازگشتی را بصورت async  برگردانی میکنند. اما چگونه میتوانیم کاری کنیم تا چندین مقدار توسط این توابع بازگشت داده شود؟ بله در این زمان باید از ویژگی به اسم Flow  در زبان برنامه نویسی کاتلین استفاده کنیم.

دوره های شبکه، برنامه نویسی، مجازی سازی، امنیت، نفوذ و ... با برترین های ایران
سرفصل های این مطلب
  1. Flow builders

Flow  یک روش پردازش استریم ها که توسط کمپانی Jetbrain  توسعه داده شده است و در دسته بندی برنامه نویسی Reactive  قرار میگیرد.  هدف این کار پردازش استریم ها به شکلی ناهمگام بود.

با استفاده از قابلیت های Flow  برای مدیریت مقادیر استریم ها میتوانید اطلاعات را با استفاده از راه های مالتی تردینگ به اشکال پیچیده تر تبدیل کنید و این کار فقط با کدنویسی چند خط ساده امکان پذیر است.

یکی از مزایای خوب Flow ها این است که فقط زمانی اجرا میشوند که یک observer یک متد یا عملگری تعلیق کننده ( suspend) مثل collect را صدا بزند و بعد از آن دیگر فعال نمیماند.

برای شروع ابتدا کتابخانه های زیر را باید وارد پروژده کنید.

implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.3.3"
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.3"

همچنین پلاگین زیر:

classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:1.3.61"
 

در صفحه اکتیویتی یک رابط کاربری با کدهای زیر ایجاد میکنیم.

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout 
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">


    <Button
        android:id="@+id/button"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        app:layout_constraintBottom_toBottomOf="parent"
        android:text="Launch Kotlin Flow"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

</androidx.constraintlayout.widget.ConstraintLayout>

در کدهای کاتلین و متد onCreate() دو متد زیر را فراخوانی میکنیم و سپس به پیاده سازی آنها میپردازیم.

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContentView(R.layout.activity_main)
    setupFlow()
    setupClicks()
}

در متد setupFlow() کدهای مربوط به پیاده سازی Flow  را قرار میدهیم و در متد setupClicks() با کلیک کردن روی دکمه اطلاعات مورد نظر را نمایش میدهیم.

قبل از این موارد یک متغیر از نوع int  نیز تعریف میکنیم.

lateinit var flow: Flow<Int>

 

در متد setupFlow اعداد را از 0 تا 10 با 500 میلی ثانیه تاخیر بین هرکدام ارسال میکنیم.

fun setupFlow(){
    flow = flow {
        Log.d(TAG, "Start flow")
        (0..10).forEach {
            // Emit items with 500 milliseconds delay
            delay(500)
            Log.d(TAG, "Emitting $it")
            emit(it)

        }
    }.flowOn(Dispatchers.Default)
}

 این کار توسط تابع emit() انجام می شود که وظیفه آن جمع آوری اطلاعات ارسال شده است و بخشی از FlowCollector می باشد.

در آخر هم از flowOn  استفاده میکنیم نوع Dispatchers  را مشخص میکند مانند  IO, Default و...

flowOn تقریبا مثل تابع subscribeOn در RXJava  است.

در متد setupClicks() نیاز داریم تا رویداد کلیک را بنویسیم و مقادیر منتشر شده را نمایش دهیم.

private fun setupClicks() {
    button.setOnClickListener {
        CoroutineScope(Dispatchers.Main).launch {
            flow.collect {
                Log.d(TAG, it.toString())
            }
        }
    }
}

با کلیک کردن روی دکمه مقادیر یک به یک نمایش داده خواهد شد.

دستور flow.collect شروع به جمع آوری اطلاعات از Flow  میکند.

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

D/MainActivity: Start flow
D/MainActivity: Emitting 0
D/MainActivity: 0
D/MainActivity: Emitting 1
D/MainActivity: 1
D/MainActivity: Emitting 2
D/MainActivity: 2
D/MainActivity: Emitting 3
D/MainActivity: 3
D/MainActivity: Emitting 4
D/MainActivity: 4
D/MainActivity: Emitting 5
D/MainActivity: 5
D/MainActivity: Emitting 6
D/MainActivity: 6
D/MainActivity: Emitting 7
D/MainActivity: 7
D/MainActivity: Emitting 8
D/MainActivity: 8
D/MainActivity: Emitting 9
D/MainActivity: 9
D/MainActivity: Emitting 10
D/MainActivity: 10

همانطور که میبینید Flow  درست زمانی شروع میشود که روی دکمه کلیک میکنیم و پیغام Start Flow نمایش داده میشود.

این همان ویژگی جالبی است که بالاتر به آن اشاره کردیم.

حالا قصد داریم تا کمی تغییرات در کدهای خودمون ایجاد کنیم.

در تصویر بالا میبینید که دستور map  را اضافه کردیم و هر آیتم را در خود ضرب کنیم یا به توان دو میرسانیم.

private fun setupFlow() {
    flow = flow {
        Log.d(TAG, "Start flow")
        (0..10).forEach {
            // Emit items with 500 milliseconds delay
            delay(500)
            Log.d(TAG, "Emitting $it")
            emit(it)
        }
    }.map {
        it * it
    }.flowOn(Dispatchers.Default)
}

 

تمام دستوراتی که بالای flowOn نوشته میشوند در ترید پس زمینه اجرا میشوند.

Flow builders

برای Flow ها یک سری امکانات دیگر نیز در نظر گرفته شده مثل Builderها که باهم بررسی میکنیم.

زمانی که بخواهیم از یک سری مجموعه اطلاعات ثابت استفاده کنیم دستور flowOf() به کمک ما می آید.

flowOf(4, 2, 5, 1, 7).onEach { delay(400) }.flowOn(Dispatcher.Default)

دستور asFlow() به ما این امکان را میدهد تا دیتا تایپ ها را به Flow  تبدیل کنیم.

(1..5).asFlow().onEach{ delay(300)}.flowOn(Dispatchers.Default)

در این مثال یک مقدار رنج از 1 تا 5 را به Flow تبدیل کردیم.


حسام رسولیان
حسام رسولیان

برنامه نویس موبایل

کارشناسی فناوری اطلاعات برنامه نویس موبایل با زبان های جاوا, کاتلین, دارت(فلاتر) CCNA,LPIC

نظرات