<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
    <channel>
        <title>نوشته های narjes Mansoori</title>
        <link>https://virgool.io/feed/@narjes.mansoori</link>
        <description>Android Developer</description>
        <language>fa</language>
        <pubDate>2026-06-16 06:25:08</pubDate>
        <image>
            <url>https://files.virgool.io/upload/users/191202/avatar/5Hl5X7.jpeg?height=120&amp;width=120</url>
            <title>narjes Mansoori</title>
            <link>https://virgool.io/@narjes.mansoori</link>
        </image>

                    <item>
                <title>مفهوم CI/CD در اندروید به زبان ساده</title>
                <link>https://virgool.io/@narjes.mansoori/%D9%85%D9%81%D9%87%D9%88%D9%85-cicd-%D8%AF%D8%B1-%D8%A7%D9%86%D8%AF%D8%B1%D9%88%DB%8C%D8%AF-%D8%A8%D9%87-%D8%B2%D8%A8%D8%A7%D9%86-%D8%B3%D8%A7%D8%AF%D9%87-p8hjzdzpwmsn</link>
                <description>حتماً برات پیش اومده که بخوای پروژه‌ات رو build کنی، apk بگیری، تست کنی و برای تیم یا مشتری بفرستی. حالا تصور کن هر بار که کد جدیدی نوشتی، این کارا خودکار انجام بشه، بدون اینکه خودت کاری بکنی!اینجاست که CI/CD وارد می‌شه.خب CI و CD یعنی چی؟مفهوم CI (یکپارچه‌سازی مداوم) یعنی هر وقت کدی تو گیت کامیت یا پوش شد، خودکار build و تست بشه تا مطمئن شیم چیزی خراب نشده.مفهوم CD (ارسال مداوم) یعنی بعد از اینکه همه چیز اوکی بود، خودکار فایل apk ساخته بشه و حتی برای تسترها یا گوگل‌پلی فرستاده بشه.🔧 توی پروژه اندروید ما چی کار می‌کنه؟مثلاً تو GitHub Actions، هر بار که push می‌کنی، این اتفاقا می‌افته:سورس کد دانلود می‌شهدستور gradlew build./ اجرا می‌شه و apk تولید می‌شه تست‌ها اجرا می‌شن (اختیاری)خب apk آپلود می‌شه روی Firebase App Distribution یا گوگل‌پلی البته این مرحله هم اختیاریه 📄 یک نمونه فایل CI ساده (GitHub Actions)name: Android CIon:  push:    branches:      - mainjobs:  build:    runs-on: ubuntu-latest    steps:      - uses: actions/checkout@v3      - uses: actions/setup-java@v3        with:          distribution: &#x27;temurin&#x27;          java-version: 17      - run: ./gradlew assembleRelease🎯 هدف این فایل چیه؟این فایل به GitHub می‌گه:«هر وقت کد من تو گیت‌هاب تغییر کرد، لطفاً خودت پروژه رو بساز (build کن) و مثلاً apk تولید کن.»مثل اینه که یه نفر دیگه (ربات GitHub) همیشه آماده‌س تا دستورهای تو رو اجرا کنه.📂 اسم و مکان فایل کجا باشه؟باید این مسیر رو بسازی:markdownCopyEditپروژه/  └── .github/       └── workflows/            └── android.yml ← این فایلهاسم فایل می‌تونه android.yml یا هر اسم دیگه باشه، فقط پسوندش .yml باشه.🧾 محتوای فایل یعنی چی؟حالا بریم خط به خط همین فایل رو بخونیم و ساده معنی کنیم:yamlCopyEditname: Android CI🟢 یعنی اسم این برنامه اتومات چیه؟ ما گذاشتیمش Android CI (CI = ساخت اتوماتیک)yamlCopyEditon:  push:    branches:      - main🟢 یعنی چه وقتی این کار رو انجام بده؟وقتی روی شاخه main کدی push بشه (یعنی وقتی شما یا هم‌تیمی‌ت کد رو روی گیت بفرسته).yamlCopyEditjobs:  build:🟢 یعنی یه کاری قراره انجام بدیم به اسم build (ساخت پروژه).yamlCopyEditruns-on: ubuntu-latest🟢 یعنی این کار روی یه کامپیوتر لینوکسی اجرا می‌شه (سرور GitHub خودش).yamlCopyEditsteps:🟢 یعنی مرحله‌ها (قدم‌ها) برای اجرا:yamlCopyEdit- name: گرفتن سورس        uses: actions/checkout@v3🟢 مرحله اول: پروژه‌ی شما رو از گیت‌هاب می‌گیره (مثل clone کردن)yamlCopyEdit- name: نصب Java        uses: actions/setup-java@v3        with:          distribution: &#x27;temurin&#x27;          java-version: 17🟢 مرحله دوم: جاوای ۱۷ نصب می‌کنه، چون اندروید بهش نیاز دارهyamlCopyEdit- name: اجرا Gradle        run: ./gradlew assembleRelease🟢 مرحله آخر: می‌گه &quot;گرادل اجرا شو&quot; و apk نسخه release رو بساز🔚 نتیجه چی میشه؟وقتی این فایل رو بذاری و کد رو به گیت‌هاب بفرستی، این اتفاق‌ها می‌افته:پروژه‌ت اتومات build میشهاگه خطایی باشه می‌فهمینیازی نیست دستی apk بسازی✅ مزایای CI/CDخیالت راحته که کدت همیشه سالمهلازم نیست apk رو دستی بسازیتیم تست می‌تونه سریع نسخه جدید رو بگیرهحرفه‌ای‌تر و تمیزتر کار می‌کنی🎯 جمع‌بندیدر واقع CI/CD مثل یه دستیار پشت صحنه‌ست که کارهای تکراری و خسته‌کننده رو خودش انجام می‌ده.اگه حتی یه پروژه کوچیک داری، یه فایل ساده CI توش بذار؛ بعداً از خودت تشکر می‌کنی 😄خب و اما GitHub Actions یه ابزار خود GitHub  که بهت کمک می‌کنه کارهای تکراری مثل build، test، deploy و... رو به‌صورت خودکار انجام بدی. بهش می‌گن ابزار &quot;اتومیشن&quot;.وقتی توی گیت‌هاب روی پروژه‌ات کد push یا pull request می‌ذاری، GitHub Actions می‌تونه خودش خودکار شروع به کار کنه.🧩 چطوری کار می‌کنه؟در واقع GitHub Actions از چیزی به اسم workflow استفاده می‌کنه. این workflow یه فایل متنیه با پسوند yml. که در بالا کامل در موردش صحبت کردیم و توش مشخص می‌کنی:چه زمانی اجرا بشه؟ (مثلاً وقتی push کردی)روی چه سیستمی اجرا بشه؟ (مثلاً Ubuntu)چه دستورهایی اجرا بشن؟ (مثلاً gradlew build./)ممنون که وقت گذاشتی و این مقاله رو تا آخر خوندی. 🙏</description>
                <category>narjes Mansoori</category>
                <author>narjes Mansoori</author>
                <pubDate>Mon, 12 May 2025 10:59:23 +0330</pubDate>
            </item>
                    <item>
                <title>آشنایی با مفهوم,Socket, WebSockets، Socket.IO و SignalR</title>
                <link>https://virgool.io/@narjes.mansoori/%D8%A2%D8%B4%D9%86%D8%A7%DB%8C%DB%8C-%D8%A8%D8%A7-%D9%85%D9%81%D9%87%D9%88%D9%85socket-websockets-socketio-%D9%88-signalr-e08up2ub1jzi</link>
                <description>در برنامه‌نویسی سوکت، دو نوع سوکت اصلی وجود دارد: سوکت کلاینت و سوکت سرور. هر دو برای برقراری ارتباط بین برنامه‌ها از طریق شبکه استفاده می‌شوند، اما عملکردهای متفاوتی دارند.سوکت کلاینت:وظیفه: به طور فعال به یک سرور از راه دور متصل می‌شود و برای ارسال و دریافت داده‌ها از سرور استفاده می‌شود.نحوه ایجاد: با استفاده از کلاس java.net.Socket و مشخص کردن آدرس IP و پورت سرور ایجاد می‌شود.فعالیت: منتظر دریافت پاسخ از سرور نمی‌باشد.مثال: یک برنامه چت که کاربر از طریق آن پیامی را به سرور ارسال می‌کند.سوکت سرور:وظیفه: به اتصالات ورودی از کلاینت‌ها گوش می‌دهد و برای ارسال و دریافت داده‌ها از کلاینت‌ها استفاده می‌شود.نحوه ایجاد: با استفاده از کلاس java.net.ServerSocket و مشخص کردن پورت گوش دادن ایجاد می‌شود.فعالیت: منتظر اتصال کلاینت‌ها و ارسال و دریافت داده‌ها از آنها است.مثال: یک سرور چت که پیام‌ها را از کلاینت‌ها دریافت می‌کند و به آنها پاسخ می‌دهد.فرض کنید می‌خواهیم یک برنامه چت ساده بسازیم که در آن چندین کاربر (کلاینت) می‌توانند به یک سرور متصل شده و با یکدیگر چت کنند.1. سوکت سرور:2. سوکت کلاینت:📷📷و اما WebSockets چیست؟در واقع WebSocket یک پروتکل ارتباطی است که به کلاینت‌ها و سرورها اجازه می‌دهد بدون نیاز به HTTP، ارتباط دو طرفه و همزمان برقرار کنند. این امر WebSockets را به گزینه‌ای ایده‌آل برای برنامه‌های Real-time مانند چت‌های آنلاین، بازی‌های چند نفره و تابلوهای امتیازات تبدیل می‌کند.تفاوت WebSockets با HTTP:الف) HTTP: یک پروتکل درخواست-پاسخ است، به این معنی که کلاینت درخواستی را به سرور ارسال می‌کند و منتظر پاسخ می‌ماند. سرور پاسخ را ارسال می‌کند و سپس اتصال بسته می‌شود. این امر برای برنامه‌های Real-time که به تبادل مداوم داده‌ها نیاز دارند، مناسب نیست.ب)‌ WebSockets: یک اتصال دو طرفه و همزمان را بین کلاینت و سرور برقرار می‌کند. این بدان معناست که هر دو طرف می‌توانند در هر زمان بدون نیاز به شروع یک درخواست جدید، داده‌ها را ارسال و دریافت کنند. این امر WebSockets را برای برنامه‌های Real-time که به تبادل فوری داده‌ها نیاز دارند، ایده‌آل می‌کند.مزایای WebSockets:به شما امکان می‌دهد بدون نیاز به بارگذاری مجدد صفحه، بین کلاینت و سرور در زمان واقعی پیام ارسال و دریافت کنید.کم تاخیر: WebSockets معمولاً تاخیر کمتری نسبت به HTTP دارند، که آنها را برای برنامه‌هایی که به واکنش سریع نیاز دارند، مانند بازی‌های آنلاین، ایده‌آل می‌کند.کارآمدی: WebSockets از نظر کارآمدی در مصرف پهنای باند نسبت به HTTP هستند، زیرا نیازی به ارسال درخواست و پاسخ‌های جداگانه برای هر پیام نیست.معایب WebSockets:پیچیدگی: WebSockets از نظر مفهومی کمی پیچیده‌تر از HTTP هستند.محدودیت های مرورگر: برخی از مرورگرهای قدیمی تر ممکن است از WebSockets پشتیبانی نکنند.موارد استفاده از WebSockets:چت‌های آنلاین: WebSockets به طور گسترده برای چت‌های آنلاین استفاده می‌شوند، زیرا به کاربران امکان می‌دهند بدون نیاز به بارگذاری مجدد صفحه، در زمان واقعی پیام ارسال و دریافت کنند.بازی‌های چند نفره: WebSockets برای بازی‌های چند نفره آنلاین نیز محبوب هستند، زیرا تاخیر کم آنها به کاربران تجربه روان‌تری می‌دهد.تابلوهای امتیازات: WebSockets برای به‌روزرسانی تابلوهای امتیازات در زمان واقعی، مانند تابلوهای امتیازات ورزشی یا تابلوهای امتیازات بازی، استفاده می‌شوند.برنامه‌های مالی: WebSockets برای برنامه‌های مالی که به داده‌های بازار در زمان واقعی نیاز دارند، مانند نمودارهای سهام و پلتفرم‌های معاملاتی، استفاده می‌شوند.در واقع WebSocket: پروتکلی برای برقراری ارتباط دو طرفه و همزمان بین کلاینت و سرور.فرض کنید در حال استفاده از یک برنامه چت آنلاین هستید. در این سناریو:شما: از یک سوکت کلاینت برای اتصال به سرور چت استفاده می‌کنید.سرور چت: از یک سوکت سرور برای گوش دادن به اتصالات ورودی از کلاینت‌ها و ارسال و دریافت داده‌ها از آنها استفاده می‌کند.سرور و کلاینت: از WebSocket برای برقراری ارتباط دو طرفه و همزمان استفاده می‌کنند تا بتوانید در زمان واقعی با دیگر کاربران چت کنید.در نهایت، WebSockets یک ابزار قدرتمند برای ایجاد برنامه‌های Real-time با استفاده از اتصالات دو طرفه و کم تاخیر بین کلاینت و سرور است. اگر به دنبال راهی برای ارتقای برنامه وب خود به سطح Real-time هستید، WebSockets گزینه مناسبی برای شما است.حالا Socket.IO چیست؟در واقع Socket.IO یک کتابخانه جاوا اسکریپت است که ارتباط دو طرفه و همزمان بین کلاینت و سرور را تسهیل می‌کند. این کتابخانه بر روی WebSockets ساخته شده است و به شما امکان می‌دهد بدون نیاز به بارگذاری مجدد صفحه، بین کلاینت و سرور در زمان واقعی پیام ارسال و دریافت کنید.مزایای استفاده از Socket.IO: به شما امکان می‌دهد بدون نیاز به بارگذاری مجدد صفحه، بین کلاینت و سرور در زمان واقعی پیام ارسال و دریافت کنید.سادگی: Socket.IO استفاده از WebSockets را آسان تر می کند و رابط کاربری ساده تری را برای ایجاد و مدیریت اتصالات شبکه ارائه می دهد.قابلیت انعطاف پذیری: Socket.IO از چندین روش حمل و نقل پشتیبانی می کند، از جمله WebSockets، HTTP Long-Polling و WebTransport، که به شما امکان می دهد از آن در طیف گسترده ای از مرورگرها و دستگاه ها استفاده کنید.جامعه بزرگ: Socket.IO دارای یک جامعه بزرگ و فعال از توسعه دهندگان است که به این معنی است که می توانید به راحتی در صورت بروز مشکل کمک پیدا کنید.حالا SignalR چیست ؟  یک کتابخانه رایگان سمت سرور است که توسط مایکروسافت توسعه یافته است و به شما امکان می دهد برنامه های وب Real-time با ارتباط دو طرفه بین کلاینت و سرور ایجاد کنید. این کتابخانه بر روی WebSockets ساخته شده است و به شما امکان می دهد بدون نیاز به بارگذاری مجدد صفحه، بین کلاینت و سرور در زمان واقعی پیام ارسال و دریافت کنید.در واقع SignalR به طور خاص برای .NET Framework و .NET Core طراحی شده است، در حالی که Socket.IO یک کتابخانه جاوا اسکریپت است که می تواند با هر زبان برنامه نویسی سمت سرور استفاده شود.در واقع SignalR یک سطح انتزاع بالاتر را نسبت به Socket.IO ارائه می دهد، که به این معنی است که استفاده از آن برای توسعه دهندگان آسان تر است.در واقع SignalR دارای جامعه کوچکتری نسبت به Socket.IO است.</description>
                <category>narjes Mansoori</category>
                <author>narjes Mansoori</author>
                <pubDate>Wed, 19 Jun 2024 16:51:13 +0330</pubDate>
            </item>
                    <item>
                <title>آموزش تست نویسی در اندروید(ادامه)</title>
                <link>https://virgool.io/@narjes.mansoori/%D8%A2%D9%85%D9%88%D8%B2%D8%B4-%D8%AA%D8%B3%D8%AA-%D9%86%D9%88%DB%8C%D8%B3%DB%8C-%D8%AF%D8%B1-%D8%A7%D9%86%D8%AF%D8%B1%D9%88%DB%8C%D8%AF%D8%A7%D8%AF%D8%A7%D9%85%D9%87-oi1c4fzifxuv</link>
                <description>خب Before@: این آنوتیشن برای مشخص کردن یک متدی است که قبل از اجرای هر تست اجرا میشود. معمولاً برای تنظیمات مورد نیاز قبل از هر تست استفاده میشود.  public class ExampleTest {      private Calculator calculator;      @Before public void setUp() {         calculator = new Calculator();     }      @Test public void testAddition() {         // کد تست    } }انوتیشن After@: این آنوتیشن برای مشخص کردن یک متدی است که بعد از اجرای هر تست اجرا میشود. معمولاً برای پاکسازی وضعیت بعد از هر تست استفاده میشود.public class ExampleTest {    private DatabaseManager databaseManager;    @After    public void tearDown() {        databaseManager.closeConnection();    }    @Test    public void testDatabaseConnection() {        // کد تست    }}حالا BeforeClass@و AfterClass@ چی هستند:مشابه Before و After هستند، اما متدهایی که با این Annotationها مشخص میشوند، قبل و بعد از اجرای همه تستها در یک کلاس اجرا میشوند.class MyTestClass {    companion object {        @BeforeClass        @JvmStatic        fun setUpClass() {            // تنظیمات قبل از اجرای همه تستها            println(&quot;Setting up before all tests...&quot;)        }        @AfterClass        @JvmStatic        fun tearDownClass() {            // پاکسازی بعد از اجرای همه تستها            println(&quot;Tearing down after all tests...&quot;)        }    }    @Test    fun testAddition() {        val result = 2 + 2        assert(result == 4)        println(&quot;Test Addition Passed&quot;)    }    @Test    fun testSubtraction() {        val result = 5 - 3        assert(result == 2)        println(&quot;Test Subtraction Passed&quot;)    }}در این مثال، متدهای setUpClass و tearDownClass به عنوان متدهایی که قبل و بعد از اجرای همه تستها باید اجرا شوند، با استفاده از Annotationهای BeforeClass@و AfterClass@تعریف شدهاند. این متدها درون یک شیء companion object تعریف شدهاند تا به عنوان متدهای استاتیک در کلاس MyTestClass شناخته شوند.حالا کمی کاربردی تر مثال بزنیم.فرض کنید میخواید ویومدل خودتون رو تست کنید و براش تست بنویسید.به مثال زیر توجه کنید:class UserProfileViewModel(private val userRepository: UserRepository) : ViewModel() {    private val _user = MutableLiveData&lt;User&gt;()    val user: LiveData&lt;User&gt;        get() = _user    fun fetchUser(userId: String) {        viewModelScope.launch {            val result = userRepository.getUserById(userId)            if (result.isSuccess) {                _user.value = result.getOrNull()            } else {                // Handle error            }        }    }}حالا بیایید یک مثال از تست نویسی برای این کلاس ViewModel بنویسیم، با استفاده از کتابخانهی Mockito و JUnit:@ExperimentalCoroutinesApi@RunWith(MockitoJUnitRunner::class)class UserProfileViewModelTest {    @get:Rule    val instantExecutorRule = InstantTaskExecutorRule()    @Mock    lateinit var userRepository: UserRepository    @Mock    lateinit var userObserver: Observer&lt;User&gt;    private lateinit var userProfileViewModel: UserProfileViewModel    @Before    fun setup() {        userProfileViewModel = UserProfileViewModel(userRepository)        userProfileViewModel.user.observeForever(userObserver)    }    @Test    fun testFetchUserSuccess() {        val userId = &quot;123&quot;        val user = User(&quot;123&quot;, &quot;John Doe&quot;)                // Mocking repository response        Mockito.&#x60;when&#x60;(userRepository.getUserById(userId)).thenReturn(Result.success(user))        // Call fetchUser method        userProfileViewModel.fetchUser(userId)        // Verify that userObserver d was called and userLiveData has the correct value        Mockito.verify(userObserver).onChanged(user)    }    @Test    fun testFetchUserError() {        val userId = &quot;123&quot;        val errorMessage = &quot;User not found&quot;        // Mocking repository response        Mockito.&#x60;when&#x60;(userRepository.getUserById(userId)).thenReturn(Result.failure(Throwable(errorMessage)))        // Call fetchUser method        userProfileViewModel.fetchUser(userId)        // Verify that userObserver d was not called (since it&#x27;s an error scenario)        Mockito.verify(userObserver, Mockito.never()).onChanged(Mockito.any())    }    @After    fun tearDown() {        userProfileViewModel.user.removeObserver(userObserver)    }}در این تست نویسی، از Mockito برای ماک کردن UserRepository و ایجاد مقادیر موک استفاده شده است. سپس دو تست برای متد fetchUser نوشته شده است. در تست اول، وقتی که UserRepository موفق باشد، اطمینان حاصل میشود که تغییرات در LiveData کاربر منعکس شود. در تست دوم، وقتی که UserRepository با خطا روبرو میشود، اطمینان حاصل میشود که تغییرات در LiveData کاربر اعمال نشود.به طور کلی برای توضیح کد بالا داریم:آمادهسازی (Setup):در مرحله آمادهسازی، ما موکهایی از کلاسهای وابسته را ایجاد میکنیم و ViewModel مورد نظر را میسازیم.در این مثال، از کتابخانهی Mockito برای ایجاد موکها استفاده میشود. همچنین یک Observer برای کاربر نیز موک شده و به LiveData کاربر ViewModel اضافه میشود.تستها:در این بخش، ما تستهای مختلف برای رفتارهای مختلف ViewModel انجام میدهیم.در این مثال، دو تست برای متد fetchUser نوشته شده است.در تست اول، ما مقدار user را به عنوان نتیجهی موفقیتآمیز از UserRepository موک میکنیم و سپس میبریم ViewModel را تا اطمینان حاصل شود که LiveData کاربر به درستی به روزرسانی شده است.در تست دوم، ما مقدار خطایی را برای UserRepository موک میکنیم و سپس میبریم ViewModel را تا اطمینان حاصل شود که LiveData کاربر در صورت بروز خطا به روزرسانی نمیشود.پایاندهی (Teardown):در این مرحله، ما تمام موکها را مخرب میکنیم و منابع مصرفی را آزاد میکنیم تا هرگونه اثرات جانبی از تستها پاک شوند.در این مثال، Observer از LiveData کاربر ViewModel حذف میشود تا منابع مصرفی را آزاد کنیم.امیدوارم مفید واقع شده باشد😊</description>
                <category>narjes Mansoori</category>
                <author>narjes Mansoori</author>
                <pubDate>Sat, 06 Apr 2024 13:52:17 +0330</pubDate>
            </item>
                    <item>
                <title>تست نویسی در برنامه نویسی اندروید</title>
                <link>https://virgool.io/@narjes.mansoori/%D8%AA%D8%B3%D8%AA-%D9%86%D9%88%DB%8C%D8%B3%DB%8C-%D8%AF%D8%B1-%D8%A8%D8%B1%D9%86%D8%A7%D9%85%D9%87-%D9%86%D9%88%DB%8C%D8%B3%DB%8C-%D8%A7%D9%86%D8%AF%D8%B1%D9%88%DB%8C%D8%AF-hse1roytwyur</link>
                <description>شروع کنید تا ببینید چیه تست نویسی؟: تست نویسی مثل اینه که وقتی یه بازی می‌سازید، قبلش یه نقشه کشیدید که چی باید بسازید و بعداً ازش برای مطمئن شدن که همه چیز درست کار می‌کنه، استفاده می‌کنید.از ابزارهای خوب استفاده کنید: مثل برنامه‌هایی که برای تست نویسی در اندروید وجود دارند. مثل JUnit برای تست واحدها یا Espresso برای تست رابط کاربری.یاد بگیرید تست‌ها چطوری نوشته می‌شن: برای هر قسمت از برنامه که می‌خواین تست بنویسید، باید بدونید که چکار می‌خواین انجام بدید و اونو بنویسید.تمرین کنید، تمرین و دوباره تمرین!: بهترین راه یادگیری تست نویسی، تمرینه. پس بیاین بنویسید و تست‌ها رو اجرا کنید و ببینید چیزایی که می‌نویسید چطوری کار می‌کنند.سوال بپرسید و از منابع آموزشی استفاده کنید: اگر چیزی رو نمی‌فهمید یا گیر کردید، سوال بپرسید. اینترنت پر از منابع آموزشیه که بهتون کمک می‌کنه تا تست نویسی رو بیشتر یاد بگیرید.همیشه به تغییرات و باگ‌ها پا پیش بگیرید: یادتون نره که گاهی وقتا برنامه‌ها تغییر می‌کنند و باگ‌هایی دارند. پس تست‌ها رو همیشه بروز نگه دارید.مطمئنا! برای تست نویسی در برنامه نویسی اندروید، می‌توانید از چندین ابزار مختلف استفاده کنید که به شما کمک می‌کنند تا تست‌های واحدها، تست‌های رابط کاربری (UI) و تست‌های ادغام را انجام دهید. در ادامه به معرفی این ابزارها می‌پردازم:معرفی JUnit: JUnit یکی از محبوب‌ترین چارچوب‌های تست برای تست واحدها در جاوا و اندروید است. این چارچوب به شما امکان می‌دهد تا تست‌های واحدها را به راحتی بنویسید و اجرا کنید. در واقع، تست واحدها مربوط به بخش‌های کوچک و مستقلی از برنامه است که می‌توانید آنها را جداگانه اجرا کنید و از صحت عملکرد آنها مطمئن شوید. این بخش‌ها معمولاً توابع، کلاس‌ها، یا قسمت‌هایی از کد هستند که مستقل از سایر بخش‌های برنامه قابل تست و ارزیابی هستند.به طور مثال، فرض کنید یک تابع دارید که محاسبه‌ی میانگین اعداد یک لیست را انجام می‌دهد. شما می‌توانید یک تست واحد برای این تابع بنویسید که لیستی از اعداد را به عنوان ورودی می‌گیرد و میانگین را محاسبه کرده و سپس از صحت محاسبات خود مطمئن شود. این تست تنها روی این تابع خاص تمرکز دارد و باعث می‌شود که از عملکرد صحیح آن اطمینان حاصل کنید.ابزار Espresso: Espresso یک چارچوب تست رابط کاربری (UI) است که برای تست کردن رفتار واقعی کاربر در برنامه‌های اندروید استفاده می‌شود. با استفاده از Espresso می‌توانید تست‌های UI را به صورت اتوماتیک انجام دهید.ابزار Mockito: Mockito یک چارچوب موکینگ است که برای ایجاد شیء مجازی (mock object) در تست‌های واحدها استفاده می‌شود. این ابزار به شما امکان می‌دهد تا تعاملاتی که برنامه شما با سایر قسمت‌های آنلاین دارد را شبیه‌سازی کنید.ابزار Robolectric: Robolectric یک چارچوب تست است که اجازه می‌دهد تا تست‌های واحدها و تست‌های ادغام را در محیط JVM اجرا کنید بدون اینکه به یک دستگاه واقعی نیاز داشته باشید. این ابزار به شما امکان می‌دهد تا تست‌های سریعتری انجام دهید.ابزار UI Automator: این ابزار توسط گوگل ارائه شده و برای تست کردن عملکرد رابط کاربری در سطح سیستم عامل اندروید استفاده می‌شود. با استفاده از UI Automator می‌توانید تست‌هایی برای بررسی عملکرد رابط کاربری در سراسر برنامه اندروید خود ایجاد کنید.این ابزارها فقط چند نمونه از ابزارهایی هستند که برای تست نویسی در برنامه نویسی اندروید مورد استفاده قرار می‌گیرند. انتخاب ابزارهای مناسب بستگی به نیازهای خاص شما و نوع تست‌هایی که قصد دارید انجام دهید دارد.بیایید با یک مثال ساده و یک مثال پیشرفته از استفاده از JUnit در برنامه نویسی اندروید آشنا شویم:مثال ساده: فرض کنید که یک کلاس ساده به نام Calculator داریم که دو عدد را جمع و تفریق می‌کند. برای تست این کلاس با استفاده از JUnit، می‌توانیم یک تست واحد برای هر یک از عملیات جمع و تفریق بنویسیم.در این مثال، ما دو تست ایجاد کردیم؛ یکی برای تابع جمع و دیگری برای تابع تفریق. ما از assertEquals برای اطمینان از اینکه نتیجه‌ی بازگشتی از تابع، مورد انتظار ما استفاده کرده‌ایم.در واقع assertEquals یک تابع است که برای اطمینان از صحت نتیجه‌ی بازگشتی از یک تابع استفاده می‌شود. آرگومان اول آن، مقدار مورد انتظار است و آرگومان دوم مقدار بازگشتی از تابع است که می‌خواهیم آن را با مقدار مورد انتظار مقایسه کنیم. اگر این دو مقدار برابر نباشند، تست ناموفق خواهد بود و خطای مربوطه نمایش داده خواهد شد. در اینجا، ما از تابع subtract کلاس Calculator استفاده میکنیم تا 3 را از 5 کم کند. مقدار بازگشتی این تابع انتظار داریم که 2 باشد. بنابراین، این تست بررسی می‌کند که آیا تابع subtract به درستی عملیات تفریق را انجام می‌دهد و نتیجه‌ی آن، همان مقدار 2 است یا خیر.تست نویسی در برنامه نویسی اندروید(ادامه)</description>
                <category>narjes Mansoori</category>
                <author>narjes Mansoori</author>
                <pubDate>Wed, 03 Apr 2024 15:54:31 +0330</pubDate>
            </item>
                    <item>
                <title>تفاوت Sealed کلاس ها و Enum  کلاس ها در کاتلین</title>
                <link>https://virgool.io/@narjes.mansoori/%D8%AA%D9%81%D8%A7%D9%88%D8%AA-sealed-%DA%A9%D9%84%D8%A7%D8%B3-%D9%87%D8%A7-%D9%88-enum-%DA%A9%D9%84%D8%A7%D8%B3-%D9%87%D8%A7-%D8%AF%D8%B1-%DA%A9%D8%A7%D8%AA%D9%84%DB%8C%D9%86-m5gackpmsro6</link>
                <description>کلاس‌های Sealed و Enum هر دو برای مدل کردن داده‌های محدود به کار می‌روند، اما با تفاوت‌های مهمی:الف - Sealed Classes (کلاس‌های مهره‌دار یا Sealed):کلاس‌های Sealed یک نوع خاص از کلاس‌ها در کاتلین هستند که با استفاده از کلیدواژه sealed اعلام می‌شوند.این کلاس‌ها می‌توانند یک مجموعه محدود از زیرکلاس‌ها را تعریف کنند. بدین معنا که تمامی زیرکلاس‌های یک کلاس Sealed باید در همان فایلی که خود کلاس Sealed در آن تعریف شده است، قرار گیرند.یکی از استفاده‌های شایع این کلاس‌ها، مدل کردن یک وضعیت محدود است. به عنوان مثال، یک وضعیت مختصر و تصمیم‌گیری بین چند گزینه.زیرکلاس‌های یک کلاس Sealed می‌توانند دارای ویژگی‌ها (properties) و روش‌های (methods) خود باشند.مثال:sealed class Result {    class Success(val data: String) : Result()    class Error(val error: Throwable) : Result()}ب - Enums:Enumerations یا Enums به یک مجموعه مقادیر معین و محدود اشاره دارند.اعضای یک Enum به صورت ثابت (static) هستند و نمی‌توان آن‌ها را در زمان اجرا تغییر داد.از Enums برای نمایش مقادیر ثابت مانند روزهای هفته یا وضعیت‌های محدود استفاده می‌شود.علاوه بر این، هر عضو از یک Enum می‌تواند مقداری ویژگی داشته باشد.مثال:enum class Direction {    NORTH, SOUTH, EAST, WEST}به طور کلی، اگر بخواهید مقادیر محدود و معین را نمایش دهید و تنها از آن‌ها به عنوان ورودی‌ها استفاده کنید، معمولا از Enums استفاده می‌شود، در حالی که اگر بخواهید با وضعیت‌های متفاوت و داده‌های مختلف مواجه شوید و یا می‌خواهید قابلیت افزودن وضعیت‌های جدید را داشته باشید، از کلاس‌های Sealed استفاده می‌کنید.</description>
                <category>narjes Mansoori</category>
                <author>narjes Mansoori</author>
                <pubDate>Sun, 03 Mar 2024 12:23:36 +0330</pubDate>
            </item>
                    <item>
                <title>آشنایی با مفهوم callbackFlow  در کروتین</title>
                <link>https://virgool.io/@narjes.mansoori/%D8%A2%D8%B4%D9%86%D8%A7%DB%8C%DB%8C-%D8%A8%D8%A7-%D9%85%D9%81%D9%87%D9%88%D9%85-callbackflow-%D8%AF%D8%B1-%DA%A9%D8%B1%D9%88%D8%AA%DB%8C%D9%86-mzr2iorlmwi5</link>
                <description>در واقع callbackFlow یک FlowBuilder در کاتلین است که به شما امکان می‌دهد یک فلو را از طریق یک Callback تولید کنید. حالتی که می‌توانید از callbackFlow استفاده کنید، زمانی است که یک کتابخانه یا API از Callback برای اعلام وقوع رویدادها یا بازخورد استفاده می‌کند و شما می‌خواهید این رویدادها را به صورت یک فلو در برنامه خود استفاده کنید.حالا با یک مثال از چگونگی استفاده از callbackFlow آشنا شویم:import kotlinx.coroutines.*// شبیه‌سازی یک کلاس LocationManagerclass LocationManager {    private var locationListener: ((Location) -&gt; Unit)? = null    // تابع برای ثبت نام listener برای به‌روزرسانی‌های مکان    fun registerForLocation(listener: (Location) -&gt; Unit) {        locationListener = listener    }    // تابع برای لغو ثبت نام listener    fun unregisterForLocation() {        locationListener = null    }    // شبیه‌سازی یک رویداد به روزرسانی مکان    fun simulateLocationUpdate(location: Location) {        locationListener?.invoke(location)    }}// تعریف کلاس Locationdata class Location(val latitude: Double, val longitude: Double)// تابع ایجاد فلوی مکانfun getLocationFlow(locationManager: LocationManager): Flow&lt;Location&gt; {    return callbackFlow {        // ثبت نام listener برای به‌روزرسانی‌های مکان        val listener: (Location) -&gt; Unit = { location -&gt;            // ارسال مکان به عنوان یک رویداد در فلو            trySend(location)        }        locationManager.registerForLocation(listener)        // لغو ثبت نام listener در زمان خاتمه فلو        awaitClose {            locationManager.unregisterForLocation()        }    }}suspend fun main() {    val locationManager = LocationManager()    // ایجاد فلوی مکان    val locationFlow = getLocationFlow(locationManager)    // اشتراک‌گذاری مکان‌ها    val job = GlobalScope.launch {        locationFlow.collect { location -&gt;            println(&quot;Received location update: $location&quot;)        }    }    // شبیه‌سازی رویدادهای به‌روزرسانی مکان    locationManager.simulateLocationUpdate(Location(51.5074, 0.1278))    locationManager.simulateLocationUpdate(Location(40.7128, -74.0060))    // منتظر ماندن برای مشاهده خروجی    delay(1000)    // لغو شبیه‌سازی و شبیه‌سازی رویداد دیگر    job.cancel()    locationManager.simulateLocationUpdate(Location(34.0522, -118.2437))}خروجی:Received location update: Location(latitude=51.5074, longitude=0.1278)
Received location update: Location(latitude=40.7128, longitude=-74.006)در این مثال، getLocationFlow یک فلو از مکان‌ها ایجاد می‌کند که به صورت یک CallBack از LocationManager استفاده می‌کند. سپس با استفاده از locationFlow.collect، ما این فلو را مصرف می‌کنیم و مکان‌هایی که از LocationManager دریافت می‌شود را چاپ می‌کنیم. simulateLocationUpdate توسط LocationManager برای شبیه‌سازی رویدادهای به روزرسانی مکان فراخوانی می‌شود.</description>
                <category>narjes Mansoori</category>
                <author>narjes Mansoori</author>
                <pubDate>Sat, 02 Mar 2024 12:41:43 +0330</pubDate>
            </item>
                    <item>
                <title>آشنایی با مفهوم Flowable در RxJava</title>
                <link>https://virgool.io/@narjes.mansoori/%D8%A2%D8%B4%D9%86%D8%A7%DB%8C%DB%8C-%D8%A8%D8%A7-%D9%85%D9%81%D9%87%D9%88%D9%85-flowable-%D8%AF%D8%B1-rxjava-gibfzo6n3lag</link>
                <description>در واقع Flowable یک نوع داده‌ی جریانی (stream) در Reactive Streams است که در کتابخانه‌ی راکتیو RxJava وجود دارد. Flowable مشابه Observable است و داده‌ها را به صورت ناهمزمان(asynchronous) و به صورت یک جریان(stream) از رویدادها منتشر می‌کند.در مقابل، Flowable از حالت سینگل (single) به جای تولید تک تک رویدادها، می‌تواند تعداد بی‌نهایتی رویداد را تولید کند و این امکان را دارد که فشار(backpressure) را در مقابل مشترکین اعمال کند. فشار به معنی کنترل و مدیریت سرعت انتقال داده‌ها است تا مشترکین قادر به پردازش و مصرف آنها باشند و از سربار بالایی جلوگیری شود.در واقع Flowable به صورت یک سری عملگر قابل ترکیب است و می‌توان با استفاده از این عملگرها رویدادها را تبدیل، فیلتر، ترکیب و تغییر داد.مثالی از استفاده از Flowable:Flowable.range(1, 1000) // یک Flowable از اعداد 1 تا 1000 را تولید می‌کند
.filter(number -&gt; number % 2 == 0) // فقط اعداد زوج را انتخاب می‌کند
.map(number -&gt; &amp;quotNumber: &amp;quot + number) // اعداد را به رشته تبدیل می‌کند
.subscribe(System.out::println); // رشته‌های نهایی را چاپ می‌کنددر این مثال، Flowable اعداد از 1 تا 1000 را تولید می‌کند و سپس با استفاده از عملگرهاfilter و map، اعداد زوج را انتخاب کرده و آنها را به رشته تبدیل می‌کند. در نهایت، با استفاده ازsubscribe، رشته‌های نهایی را چاپ می‌کند.</description>
                <category>narjes Mansoori</category>
                <author>narjes Mansoori</author>
                <pubDate>Sat, 02 Mar 2024 11:15:59 +0330</pubDate>
            </item>
                    <item>
                <title>آشنایی با مفهوم StateFlow و SharedFlow</title>
                <link>https://virgool.io/@narjes.mansoori/%D8%A2%D8%B4%D9%86%D8%A7%DB%8C%DB%8C-%D8%A8%D8%A7-%D9%85%D9%81%D9%87%D9%88%D9%85-stateflow-%D9%88-sharedflow-gvrwqm8zn9ei</link>
                <description>قبل از مطالعه این پست به عنوان پیشنیاز پست قبلی رو مطالعه کنید.خب StateFlow و SharedFlow دو کلاس در کتابخانه Kotlin Coroutines می‌باشند که به شما امکان مدیریت و جریان داده در برنامه‌های کاتلین را می‌دهند. با این حال، این دو کلاس تفاوت‌های مهمی دارند. بیایید به طور جزئی‌تر به هر کدام نگاه کنیم:StateFlow:در واقع StateFlow یک جریان داده است که وضعیت (state) فعلی را نشان می‌دهد. هر بار که وضعیت تغییر می‌کند،مقدار جدید به همه مشترکین (Subscribers ها)ارسال می‌شود.در واقع StateFlow یک روش push است،به این معنی که مقدار جدید به صورت فعال از تولید کننده به مشترکین ارسال می‌شود.در واقع StateFlow یک Flowable است و می‌تواند یک مقدار اولیه داشته باشد.برای دسترسی به مقدار فعلی StateFlow، می‌توانید از ویژگی value استفاده  کنید.مثال: فرض کنید که یکViewModel داریم که یکStateFlow برای نمایش تعداد لایک‌ها در یک پست اینستاگرام را دارد. وقتی کاربر لایک می‌کند، تعداد لایک‌ها به صورت فعال تغییر می‌کند و به تمامی مشترکین ارسال می‌شود.val likesStateFlow = MutableStateFlow(0) // مقدار اولیه 0
// تغییر تعداد لایک‌ها
fun likePost() {
likesStateFlow.value += 1
}
// مشترک شدن در StateFlow
viewModelScope.launch {
likesStateFlow.collect { likes -&gt;
// نمایش تعداد لایک‌ها به کاربر
println(&amp;quotLikes: $likes&amp;quot)
}
}
// اجرای تغییر تعداد لایک‌ها
likePost() // خروجی: Likes: 1
likePost() // خروجی: Likes: 2در مقابل SharedFlow به عنوان یک جریان داده مشترک عمل می‌کند که می‌تواند به چندین مشترک ارسال شود.در واقع SharedFlow یک روش pull است، به این معنی که مشترکین برای دریافت مقادیر جدید از آن باید به صورت فعال از کلاس  Flow.collect استفاده کنند.در واقع SharedFlow می‌تواند یک مقدار اولیه داشته باشد، اما در مقابل StateFlow، وقتی یک مشترک جدید به جریان می‌پیوندد، مقدار اولیه ارسال نمی‌شود.در واقع SharedFlow از استراتژی‌های مختلفی برای مدیریت جریان استفاده می‌کند، مانند استراتژی Latest, Buffer, Drop, Conflate و غیره.مثال: فرض کنید که دو فعالیت در برنامه‌ی شما اتفاق می‌افتد که باید از یک SharedFlow برای ارسال رویدادها استفاده کنند. در این حالت، هر بار که یک رویداد اتفاق می‌افتد، مقدار جدید به تمامی مشترکین ارسال می‌شود.vale ventSharedFlow = MutableSharedFlow&lt;String&gt;()
// ارسال یک رویداد
fun sendEvent(event: String) {
viewModelScope.launch {
eventSharedFlow.emit(event)
}
}
// مشترک شدن در SharedFlow
viewModelScope.launch {
eventSharedFlow.collect { event -&gt;
// نمایش رویداد به کاربر
println(&amp;quotEvent: $event&amp;quot)
}
}
// ارسال رویداد
sendEvent(&amp;quotClick&amp;quot)
sendEvent(&amp;quotSwipe&amp;quot)خروجی:Event: Click
Event: Swipeبنابراین، StateFlow و SharedFlow هر دو برای مدیریت و جریان داده استفاده می‌شوند، اما تفاوت‌های مهمی در روش ارسال داده و مشترک شدن در آنها وجود دارد. StateFlow وضعیت فعلی را نشان می‌دهد و به صورت فعال اطلاعات را به مشترکین ارسال می‌کند، در حالی کهSharedFlow به صورتpull عمل می‌کند و مشترکین باید به صورت فعال داده‌ها را دریافت کنند.بیایید با مثال‌هایی از کاربردهای StateFlow وSharedFlow آشنا شویم :کاربرد StateFlow: فرض کنید یک برنامه داریم که یک شمارنده را نشان می‌دهد و کاربر می‌تواند با دکمه‌های افزایش و کاهش مقدار آن را تغییر دهد. در این حالت، می‌توانیم از StateFlow برای نگهداری و نمایش مقدار فعلی شمارنده استفاده کنیم.val counterStateFlow = MutableStateFlow(0)
// افزایش شمارنده
fun incrementCounter() {
counterStateFlow.value += 1
}
// کاهش شمارنده
fun decrementCounter() {
counterStateFlow.value -= 1
}
// مشترک شدن در StateFlow
viewModelScope.launch {
counterStateFlow.collect { counter -&gt;
// نمایش مقدار فعلی شمارنده به کاربر
println(&amp;quotCounter: $counter&amp;quot)
}
}
// افزایش شمارنده
incrementCounter() // خروجی: Counter: 1
incrementCounter() // خروجی: Counter: 2
decrementCounter() // خروجی: Counter: 1در این مثال، هر بار که مقدار شمارنده تغییر می‌کند، مقدار جدید به صورت فعال به تمامی مشترکین ارسال می‌شود و مشترکین مقدار فعلی را دریافت می‌کنند.کاربرد SharedFlow: فرض کنید یک برنامه داریم که بهصورت زنده نرخ قلب کاربر را نمایش می‌دهد. در این حالت، می‌توانیم از SharedFlow برای ارسال و دریافت نرخ قلب استفاده کنیم.val heartRateSharedFlow = MutableSharedFlow&lt;Int&gt;()
// ثبت نرخ قلب جدید
fun recordHeartRate(heartRate: Int) {
viewModelScope.launch {
heartRateSharedFlow.emit(heartRate)
}
}
// مشترک شدن در SharedFlow
viewModelScope.launch {
heartRateSharedFlow.collect { heartRate -&gt;
// نمایش نرخ قلب به کاربر
println(&amp;quotHeart Rate: $heartRate&amp;quot)
}
}
// ثبت نرخ قلب جدید
recordHeartRate(75) // خروجی: Heart Rate: 75
recordHeartRate(80) // خروجی: Heart Rate: 80در این مثال، هر بار که نرخ قلب جدید ثبت می‌شود، مقدار جدید به تمامی مشترکین ارسال می‌شود و مشترکین مقدار جدید را دریافت می‌کنند.استفاده از StateFlow یا SharedFlow بستگی به نوع کاربرد و نیازهای خاص شما دارد. در ادامه، مثال‌هایی را بررسی می‌کنیم و توضیح می‌دهیم که کدام یک از این دو راه حل مناسب‌تر است:استفاده از StateFlow: معمولاً StateFlow برای مواردی مناسب است که مقدار فعلی داده مهم است و نیاز به ردیابی تغییرات فوری      داریم. به عنوان مثال، فرض کنید در یک برنامه لیستی از پیام‌ها را نمایش می‌دهیم و کاربر می‌تواند پیام‌ها را حذف کند. در این حالت، می‌توانیم از StateFlow برای نگهداری و نمایش لیست پیام‌ها استفاده کنیم.val messagesStateFlow = MutableStateFlow(listOf&lt;String&gt;())
// حذف پیام
fun deleteMessage(message: String) {
messagesStateFlow.value = messagesStateFlow.value - message
}
// مشترک شدن در StateFlow
viewModelScope.launch {
messagesStateFlow.collect { messages -&gt;
// نمایش لیست پیام‌ها به کاربر
println(&amp;quotMessages: $messages&amp;quot)
}
}
// حذف پیام
deleteMessage(&amp;quotHello&amp;quot) // خروجی: Messages: [ ]
deleteMessage(&amp;quotHi&amp;quot) // خروجی: Messages:  [ ]در این مثال، هر بار که یک پیام حذف می‌شود، لیست پیام‌ها به صورت فعال به تمامی مشترکین ارسال می‌شود و مشترکین لیست پیام‌های به‌روزشده را دریافت می‌کنند.استفاده از SharedFlow: معمولاً SharedFlow برای مواردی مناسب است که نیاز به انتشار یک رویداد یا اطلاعات در سراسر برنامه      داریم. به عنوان مثال، فرض کنید یک برنامه داریم که اعلان‌ها را نمایش می‌دهد و هر بار که یک اعلان جدید دریافت می‌شود، باید آن را به تمامی صفحات برنامه ارسال کنیم. در این حالت، می‌توانیم از SharedFlow برای ارسال اعلان‌ها استفاده کنیم.val notificationSharedFlow = MutableSharedFlow&lt;String&gt;()
// دریافت اعلان جدید
fun receiveNotification(notification: String) {
viewModelScope.launch {
notificationSharedFlow.emit(notification)
}
}
// مشترک شدن در SharedFlow
viewModelScope.launch {
notificationSharedFlow.collect { notification -&gt;
// نمایش اعلان به کاربر
println(&amp;quotNew Notification: $notification&amp;quot)
}
}
// دریافت اعلان جدید
receiveNotification(&amp;quotNew message!&amp;quot) // خروجی: New Notification: New message!
receiveNotification(&amp;quotReminder!&amp;quot) // خروجی: New Notification: Reminder!در این مثال، هر بار که یک اعلان جدید دریافت می‌شود، اعلان به تمامی مشترکین ارسال می‌شود و مشترکین اعلان جدید را دریافتند.خب حالا باز یک مثال ملموس تر در پروژه های اندرویدی میخواییم بزنیم:در مثال اول، سناریویی توضیح داده شده است که در آن لیستی از کاربران از شبکه دریافت و در رابط کاربری نمایش داده می‌شود. در ViewModel از StateFlow استفاده می‌شود. این StateFlow با نام usersStateFlow ایجاد شده و مقدار اولیه آن به صورت UiState.Loading تنظیم می‌شود. در Activity نیز یک collector برای این StateFlow وجود دارد.وقتی که Activity باز می‌شود، فعالیت به collect مشترک می‌شود و مقدار اولیه یعنی وضعیت Loading از StateFlow جمع‌آوری می‌شود.سپس، ViewModel اطلاعات را از شبکه دریافت می‌کند و آنها را به usersStateFlow ارسال می‌کند.usersStateFlow.value = UiState.Success(usersFromNetwork)سپس، collector در Activity داده‌های کاربران را دریافت و در رابط کاربری نمایش می‌دهد.در صورتی که تغییر جهت دستگاه رخ دهد و ViewModel باقی بماند، collector موجود در Activity دوباره به collect مشترک می‌شود و داده‌های موجود در usersStateFlow که لیست کاربران دریافت شده از شبکه است را جمع‌آوری می‌کند.مزیت استفاده از StateFlow در اینجا این است که نیازی به درخواست جدید از شبکه نیست و داده‌های قبلی در ViewModel باقی می‌مانند.سپس به بررسی استفاده از SharedFlow می‌پردازیم. در مثال دوم، سناریویی توضیح داده شده است که در آن در صورتی که یک وظیفه با شکست مواجه شود، باید Snackbar نمایش داده شود. در ViewModel از SharedFlow استفاده می‌شود. این SharedFlow با نام showSnackbarSharedFlow ایجاد شده و یک collector برای آن در Activity وجود دارد.وقتی که Activity باز می‌شود، فعالیت به collect مشترک می‌شود، اما در اینجا چیزی جمع‌آوری نمی‌شود زیرا از SharedFlow استفاده شده است.سپس، ViewModel کار را شروع کرده و در صورتی که با شکست مواجه شود، مقدار true را به showSnackbarSharedFlow ارسال می‌کند.showSnackbarSharedFlow.emit(true)سپس، collector در Activity مقدار true را دریافت و Snackbar را نمایش می‌دهد.در صورتی که تغییر جهت دستگاه رخ دهد و ViewModel باقی بماند، collector موجود در Activity دوباره به collect مشترک می‌شود، اما در اینجا چیزی جمع‌آوری نمی‌شود زیرا SharedFlow آخرین مقدار را نگه‌داری نمی‌کند و همین امر مطلوب است زیرا ما نباید Snackbar را در تغییر جهت دستگاه مجدداً نمایش دهیم.مزیت استفاده از SharedFlow در اینجا این است که Snackbar را مجدداً نمایش نمی‌دهد همانطور که مورد نیاز است.حالا به استفاده از StateFlow به جای SharedFlow می‌پردازیم. در مثال سوم، در ViewModel از StateFlow استفاده می‌شود. این StateFlow با نام showSnackbarStateFlow ایجاد شده و مقدار اولیه آن به صورت false تنظیم می‌شود. در Activity نیز یک collector برای این StateFlow وجود دارد.وقتی که Activity باز می‌شود، فعالیت به collect مشترک می‌شود و مقدار اولیه یعنی false از StateFlow جمع‌آوری می‌شود.سپس، ViewModel کار را شروع کرده و در صورتی که با شکست مواجه شود، مقدار true را به showSnackbarStateFlow ارسال می‌کند.showSnackbarStateFlow.value = trueسپس، collector در Activity مقدار true را دریافت و Snackbar را نمایش می‌دهد.در صورتی که تغییر جهت دستگاه رخ دهد و ViewModel باقی بماند، collector موجود در Activity دوباره به collect مشترک می‌شود و مقدار true که آخرین مقدار است را جمع‌آوری می‌کند. این باعث می‌شود Snackbar مجدداً نمایش داده شود و این امر مطلوب نیست.معایب استفاده از StateFlow در اینجا این است که Snackbar را مجدداً نمایش می‌دهد که لازم نیست.بنابراین، در این مورد باید از SharedFlow به جای StateFlow استفاده کنیم.نکته : عملگر replay در SharedFlow کنترل می‌کند که آیا یک SharedFlow می‌تواند مقادیر قبلی را برای مشترکین جدیدی که به آن متصل می‌شوند، ارسال کند یا خیر. با استفاده از این عملگر، می‌توانید تعیین کنید که چند مقدار قبلی برای مشتریان جدید بازیابی شود.مثال زیر نشان می‌دهد چگونه با استفاده از عملگر replay می‌توان مقادیر قبلی را برای مشترکین جدید بازیابی کرد:suspend fun main() {    val sharedFlow = MutableSharedFlow&lt;Int&gt;(replay = 2) // بازیابی 2 مقدار قبلی    // مشترک 1    val job1 = sharedFlow.collect { println(&quot;مشترک 1: $it&quot;) }    // مقادیر را ارسال می‌کنیم    sharedFlow.emit(1)    sharedFlow.emit(2)    sharedFlow.emit(3)    delay(1000) // تأخیر برای جلوگیری از مشاهده مقادیر تا اینجا توسط مشترک دیگر    // مشترک 2    val job2 = sharedFlow.collect { println(&quot;مشترک 2: $it&quot;) }    // لغو مشترکین    job1.cancel()    job2.cancel()}خروجی:output: مشترک 1: 1مشترک 1: 2مشترک 1: 3مشترک 2: 2مشترک 2: 3در این مثال، با استفاده از replay = 2، مقادیر قبلی دو مقدار ارسالی (1 و 2) به مشترک جدید (مشترک 2) بازیابی می‌شوند. این به مشترک جدید اجازه می‌دهد تا مقادیر قبلی را دریافت کرده و سپس از آخرین مقدار (3) شروع به دریافت مقادیر کند.حالا که درک خوبی از StateFlow و SharedFlow داریم، می‌توانیم به راحتی تصمیم بگیریم که در هر مورد از کدام یک استفاده کنیم.نتیجه گیری : بر اساس مطالب فوق، تفاوت‌های بین StateFlow و SharedFlow به شرح زیر است:خب StateFlow یک Hot Flow است، به این معنی که نیاز به یک مقدار اولیه دارد و این مقدار را به طور فوری به مشترکین ارسال می‌کند.فقط مقدار آخرین مقدار شناخته شده را ارسال می‌کند.دارای خاصیت value است که می‌توانیم مقدار فعلی را بررسی کنیم. همچنین یک تاریخچه با یک مقدار را نگهداری می‌کند که می‌توانیم آن را مستقیماً بدون نیاز به جمع‌آوری دریافت کنیم.مقادیر تکراری متوالی را ارسال نمی‌کند. فقط زمانی مقدار را ارسال می‌کند که از مقدار قبلی متمایز باشد.شباهتی به LiveData دارد به جز آگاهی از LifeCycle . برای اضافه کردن آگاهی از LifeCycle به آن، باید از scope repeatOnLifecycle با StateFlow استفاده شود، در این صورت مانند LiveData عمل خواهد کرد.خب SharedFlow:یک Hot Flow است.نیازی به یک مقدار اولیه ندارد، بنابراین به طور پیش فرض هیچ مقداری را ارسال نمی‌کند.می‌توان با استفاده از اپراتور replay تنظیم کرد که چندین مقدار قبلی را ارسال کند.خاصیت value را ندارد.تمامی مقادیر را ارسال می‌کند و اهمیتی به تمایز از مقدار قبلی نمی‌دهد. بنابراین مقادیر تکراری متوالی را نیز ارسال می‌کند.شباهتی به LiveData ندارد.امیدوارم مفید واقع شده باشد.😊</description>
                <category>narjes Mansoori</category>
                <author>narjes Mansoori</author>
                <pubDate>Sat, 02 Mar 2024 10:30:12 +0330</pubDate>
            </item>
                    <item>
                <title>آشنایی با مفهوم Cold Flow و Hot Flow</title>
                <link>https://virgool.io/@narjes.mansoori/%D8%A2%D8%B4%D9%86%D8%A7%DB%8C%DB%8C-%D8%A8%D8%A7-%D9%85%D9%81%D9%87%D9%88%D9%85-cold-flow-%D9%88-hot-flow-hziz90h7wxp4</link>
                <description>سلام بچه ها امیدوارم که حالتون خوب باشه .بازم اومدیم با یه پست دیگه از مفاهیم مربوط به flow ها .پیشنهاد میکنم پست های دیگه مرتبط با این مبحث رو مطالعه کنید تا بتونید مفاهیم رو کامل درک کنید.خب ببینیم Cold Flow چی هست؟فقط زمانی داده‌ها را ارسال می‌کنند که گردآورنده یا collector  وجود دارد.اطلاعات را ذخیره نمی‌کنند.نمی‌توانند چندین collector داشته باشند.خب اینا از ویژگی های Cold Flow ها هستند.نگران نباشید در ادامه با مثال بیشتر توضیح میدم که کامل متوجه بشین.در مثال زیر یکCold Flow تعریف می‌شود که اعداد ۱ تا ۵ را در فواصل یک ثانیه‌ای ارسال می‌کند. سپس دو collector برای این فلو تعریف می‌شود. اولین collector مقادیر را چاپ می‌کند و دومین collector نیز پس از ۲.۵ ثانیه شروع به جمع‌آوری مقادیر می‌کند.val numbersColdFlow = getNumbersColdFlow()numbersColdFlow    .collect {        println(&quot;1st Collector: $it&quot;)    }delay(2500)numbersColdFlow    .collect {        println(&quot;2nd Collector: $it&quot;)    }خروجی کد بالا به صورت زیر خواهد بود1st Collector: 11st Collector: 21st Collector: 31st Collector: 41st Collector: 52nd Collector: 12nd Collector: 22nd Collector: 32nd Collector: 42nd Collector: 5ارسال خب همانطور که در خروجی بالا میبینیم به ازای هر collector فلو از ابتدا شروع به انتشار دیتاها میکند.حالا ببینیم Hot Flow چی هستداده‌ها را حتی زمانی که collector وجود ندارد، منتشر می‌کنند.می‌توانند اطلاعات را ذخیره کنند.می‌توانند چندین collector داشته باشند.:در این مثال، یک Hot Flow تعریف می‌شود که اعداد ۱ تا ۵ را در فواصل یک ثانیه‌ای ارسال می‌کند. سپس دو collector برای این فلو تعریف می‌شود. اولین collector مقادیر را چاپ می‌کند و دومین collector نیز پس از ۲.۵ ثانیه شروع به جمع‌آوری مقادیر می‌کند.val numbersHotFlow = getNumbersHotFlow()numbersHotFlow    .collect {        println(&quot;1st Collector: $it&quot;)    }delay(2500)numbersHotFlow    .collect {        println(&quot;2nd Collector: $it&quot;)    }خروجی:1st Collector: 11st Collector: 21st Collector: 31st Collector: 41st Collector: 52nd Collector: 32nd Collector: 42nd Collector: 5در فلوهای گرم، collector ها از جایی که شروع به جمع‌آوری کرده‌اند، مقادیر را دریافت می‌کنند. در مثال بالا، collector اول همه مقادیر را دریافت می‌کند، اما collector دوم فقط مقادیری را دریافت می‌کند که پس از ۲.۵ ثانیه ارسال شده‌اند، چون پس از این زمان شروع به جمع‌آوری کرده است.همچنین می‌توانیم تنظیماتی را برای ذخیره‌سازی داده‌ها در فلوهای گرم انجام دهیم. به عنوان مثال، می‌توانیم تنظیم کنیم که فقط آخرین مقدار ارسالی ذخیره شود.آشنایی با StateFlow و SharedFlow</description>
                <category>narjes Mansoori</category>
                <author>narjes Mansoori</author>
                <pubDate>Wed, 28 Feb 2024 17:27:48 +0330</pubDate>
            </item>
                    <item>
                <title>ترمینال اپراتور ها (Terminal Operators)در flow ها</title>
                <link>https://virgool.io/@narjes.mansoori/%D8%AA%D8%B1%D9%85%DB%8C%D9%86%D8%A7%D9%84-%D8%A7%D9%BE%D8%B1%D8%A7%D8%AA%D9%88%D8%B1-%D9%87%D8%A7-terminal-operators%D8%AF%D8%B1-flow-%D9%87%D8%A7-ut1juqg0wkuf</link>
                <description>ترمینال اپراتورها در فلوها عملیاتی هستند که به واقع فلو را خاتمه می‌دهند . این اپراتورها اجازه می‌دهند تا مقادیر را از فلوها جمع آوری کنید و انجام عملیات‌های مختلفی روی آن‌ها داشته باشید.مهمترین ترمینال اپراتور collect است که به عنوان گردآورنده عمل می‌کند. این اپراتور اجازه می‌دهد تا مقادیری که در فلوها تولید شده‌اند را جمع آوری کنید و آن‌ها را به صورت مشخص چاپ یا استفاده دیگری از آن‌ها داشته باشید.همچنین ترمینال اپراتورها می‌توانند عملیاتی مانند کاهش (reduce) را اعمال کنند که به ازای هر آیتم ارسالی یک تابع مشخص را اعمال می‌کنند و نهایتاً یک مقدار نهایی را ارسال می‌کنند.به عنوان مثال، در کد زیر، ما از ترمینال اپراتور collect برای جمع‌آوری مقادیر از یک فلو استفاده می‌کنیم:(1..5).asFlow()    .filter {        it % 2 == 0    }    .map {        it * it    }    .collect {        Log.d(TAG, it.toString())    }در این مثال، با استفاده از ترمینال اپراتور collect، ما مقادیری که از فلو تولید شده‌اند را جمع‌آوری کرده و آن‌ها را به صورت مشخص چاپ می‌کنیم.بنابراین، ترمینال اپراتورها اساسی‌ترین بخش از یک فلو هستند که با استفاده از آن‌ها می‌توانید مقادیر را جمع آوری کرده و با آن‌ها کار کنید.پس، اگر شما فقط خط زیر را بنویسید، فلو آغاز نمی‌شود:(1..5).asFlow()    .filter {        it % 2 == 0    }    .map {        it * it    }شما باید از عملگر ترمینال برای شروع آن استفاده کنید، در این مورد، collect.حالا بیایید عملگر ترمینال دیگری را که عملگر کاهش (reduce) است ببینیم.در عملگر کاهش، یک تابع به هر آیتم ارسال شده اعمال می‌شود و مقدار نهایی را ارسال می‌کند. به عنوان مثال:val result = (1..5).asFlow()
    .reduce { a, b -&gt; a + b }
Log.d(TAG, result.toString())در اینجا، نتیجه 15 خواهد بود.خب در مورد این عملگر توضیحاتی بدم :در مرحله اولیه، داریم 1، 2، 3، 4، 5 که قرار است ارسال شوند.اولاً، a = 0 و b = 1 است که بر اساس مراحل، تغییر می‌کنند.مرحله 1:a = 0، b = 1a = a + b = 0 + 1 = 1مرحله 2:a = 1، b = 2a = a + b = 1 + 2 = 3و غیره...در نهایت، مقدار نهایی به عنوان 15 خواهد بود.</description>
                <category>narjes Mansoori</category>
                <author>narjes Mansoori</author>
                <pubDate>Wed, 28 Feb 2024 17:03:42 +0330</pubDate>
            </item>
                    <item>
                <title>آشنایی با Flow Builder در کاتلین</title>
                <link>https://virgool.io/@narjes.mansoori/%D8%A2%D8%B4%D9%86%D8%A7%DB%8C%DB%8C-%D8%A8%D8%A7-flow-builder-%D8%AF%D8%B1-%DA%A9%D8%A7%D8%AA%D9%84%DB%8C%D9%86-cukj9buo5tmc</link>
                <description> به طور خلاصه، چهار نوع سازنده‌ی اصلی برای ایجاد فلوها در کروتین وجود دارد که به شرح زیر است:الف- ()flowOf: این نوع سازنده برای ایجاد یک فلو از مجموعه‌ای از آیتم‌ها استفاده می‌شود. به عبارت دیگر، شما می‌توانید یک فلو را با دادن مقادیر مختلف به عنوان ورودی به ()flowOf ایجاد کنید.ب- ()asFlow: این تابع نوع تبدیل است که به شما کمک می‌کند یک داده را به فلو تبدیل کنید. به عنوان مثال، شما می‌توانید یک دنباله از اعداد را به عنوان ورودی به()asFlow بدهید و آن را به یک فلو تبدیل کنید.ج- {}flow: این نوع سازنده فلوها را بر اساس یک بلاک کد تعریف می‌کند. با استفاده از این سازنده، شما می‌توانید فلوهایی را با قوانین خاص و کاربردهای مختلفی تعریف کنید.د- {}channelFlow: این سازنده فلوها را با استفاده از تابع send ایجاد می‌کند که توسط خود سازنده ارائه می‌شود. با استفاده از این سازنده، می‌توانید قابلیت‌های پیچیده‌تری را برای فلوهای خود ایجاد کنید، مانند ارسال داده‌ها به صورت غیرهمزمان.حالا بیایید با استفاده از مثال‌ها نحوه‌ی ساخت فلوها با استفاده از سازنده‌های مختلف را ببینیم:مثال ۱: استفاده از ()flowOffun main() {    flowOf(4, 2, 5, 1, 7)        .collect {            Log.d(TAG, it.toString())        }}خروجی:output: 42517مثال ۲: استفاده از ()asFlowfun main() {    (1..5).asFlow()        .collect {            Log.d(TAG, it.toString())        }}خروجی:output: 12345مثال ۳: استفاده از {}flowfun main() = runBlocking {    flow {        (0..10).forEach {            emit(it)        }    }    .collect {        Log.d(TAG, it.toString())    }}خروجی:output: 012345678910مثال ۴: استفاده از {}channelFlowfun main() = runBlocking {    channelFlow {        (0..10).forEach {            send(it)        }    }    .collect {        Log.d(TAG, it.toString())    }}خروجی:output: 012345678910در این مثال‌ها، با استفاده از انواع مختلف سازنده‌های فلو، فلوهایی با مقادیر مختلف ایجاد شده‌اند و سپس مقادیر آن‌ها با استفاده از تابع collect  چاپ شده‌اند.</description>
                <category>narjes Mansoori</category>
                <author>narjes Mansoori</author>
                <pubDate>Wed, 28 Feb 2024 14:53:55 +0330</pubDate>
            </item>
                    <item>
                <title>مفهوم Flow در کاتلین</title>
                <link>https://virgool.io/@narjes.mansoori/%D9%85%D9%81%D9%87%D9%88%D9%85-flow-%D8%AF%D8%B1-%DA%A9%D8%A7%D8%AA%D9%84%DB%8C%D9%86-tmmd4bafrdxu</link>
                <description>سلام بچه ها امیدوارم که حالتون خوب باشه .خب با هم بریم در مورد بحث شیرین و در عین حال خیلی مهم flow ها در کاتلین بحث کنیم.خب اول از همه اینکه ببینیم اصلا flow ها چی هستند؟خب Flow در کاتلین کروتین یک راه برای پردازش داده‌های جریانی (streaming data) است که امکان تولید، تبدیل و جمع‌آوری داده‌ها را فراهم می‌کند. برای درک بهتر، می‌توانیم Flow را با یک لوله فلزی که داده‌ها از آن عبور می‌کنند تصور کنیم. حالا بیایید به مرور اجزای اصلی Flow بپردازیم:الف - Flow Builder (سازنده Flow):Flow Builder مسئول تولید داده‌ها و ارسال آنها در جریان است. مانند یک سخنران که وظیفه‌ی انجام یک کار و گفتن اطلاعات را دارد.می‌توانیم از توابعی مانند ()flowOf یا {}flow  برای ساخت یک Flow استفاده کنیم.برای آشنایی بیشتر با Flow Builder این پست رو نگاه بنداز.ب - Operator (عملگر):عملگرها به تبدیل داده‌های جریانی از یک فرمت به فرمت دیگر کمک می‌کنند.مانند یک مترجم که وظیفه‌ی ترجمه داده‌ها را بین دو زبان یا فرمت مختلف دارد.عملگرها به ما امکان می‌دهند داده‌ها را تغییر داده و به شکلی دیگر در خروجی Flow بازگردانی کنیم.برخی از عملگرها شامل map، filter، transform و ... می‌شوند.اینا هم در ادامه کامل توضیح خواهیم داد.ج - Collector (جمع‌آورنده):جمع‌آورنده مسئول جمع‌آوری و دریافت داده‌های جریانی از Flow است.مانند یک شنونده که می‌شنود و اطلاعات را دریافت می‌کند.در کروتین، معمولاً از تابع collect که خود یک ترمینال اپراتور است برای جمع‌آوری داده‌ها استفاده می‌شود.حالا بیایید یک مثال را بررسی کنیم و فرآیند Flow را برای دانلود تصویر در اندروید توضیح دهیم:// Assume we have a function to download an image with progresssuspend fun downloadImage(): Flow&lt;Int&gt; = flow {    // Simulate download progress    (1..10).forEach {        delay(500) // Simulate delay        emit(it * 10) // Emit progress percentage    }}fun main() {    val downloadJob = CoroutineScope(Dispatchers.IO).launch {        downloadImage()            .map { progress -&gt; &quot;Download progress: $progress%&quot; }            .collect { message -&gt;                println(message) // Print download progress message            }    }    // Wait for download job to complete    runBlocking {        downloadJob.join()    }}در این مثال، یک Flow برای دانلود تصویر با پیشرفت دانلود به صورت درصدی تعریف شده است. سپس در یک CoroutineScope با استفاده از launch، این Flow دانلود فراخوانی می‌شود. سپس با استفاده از عملگر map، پیشرفت دانلود به پیام‌های متنی تبدیل می‌شود و در نهایت با استفاده از جمع‌آورنده collect، این پیام‌ها چاپ می‌شوند.خروجی این کد  صورت زیر می باشد:Download progress: 10%Download progress: 20%Download progress: 30%Download progress: 40%Download progress: 50%Download progress: 60%Download progress: 70%Download progress: 80%Download progress: 90%Download progress: 100%در اینجا، ما با استفاده از Flow Builder، Operator و Collector می‌توانیم فرآیندی مانند دانلود تصویر را کنترل و مدیریت کنیم و پیشرفت آن را به کاربر نمایش دهیم.در نتیجه Flow در کاتلین کروتین  به شما امکان می‌دهد با داده‌های جریانی (streaming data) کار کنید. به طور خلاصه، Flow یک راه برای پردازش داده‌ها است که می‌توانید آن را به صورت همروند، بازگشتی و بی‌بلوکینگ در برنامه‌های خود استفاده کنید.در Flow، داده‌ها به صورت پیوسته جریان دارند، به این معنی که آنها به طور متوالی و با تأخیرهای مختلفی تولید می‌شوند و شما می‌توانید این داده‌ها را به طور همروند دریافت و پردازش کنید. Flow به شما اجازه می‌دهد تا برنامه‌هایی را بنویسید که با مقداری بیشتر از حافظه بهبودیافته و کارایی بهتری داشته باشند. از آنجا که Flow بر پایه کاتلین کروتین است، می‌توانید از امکانات همروندی کروتین مانند ایجاد تردهای جدید، مدیریت خطا، تأخیر و غیره نیز استفاده کنید. در کاتلین کروتین، Flow‌ها بصورت پیشفرض بر روی تردهای اصلی اجرا نمی‌شوند. به جای این کار، آنها بصورت معمول در تردهایی اجرا می‌شوند که با flowOn تعیین شده‌اند. این به شما امکان می‌دهد تا ترتیب اجرای Flow‌ها را مدیریت کرده و آنها را بر روی تردهای متفاوتی که بهینه‌تر بازیابی اطلاعات را فراهم می‌کنند، اجرا کنید.برای مثال، اگر شما یک Flow را بر روی یک ترد پشته‌ای (IO) اجرا کنید و از آن برای دانلود فایل‌ها استفاده کنید، Flow می‌تواند داده‌های جریانی را در تردهای پشته‌ای اجرا کند که مناسب برای فعالیت‌های ورودی و خروجی است. همچنین می‌توانید از flowOn استفاده کنید تا Flow را بر روی تردهای مختلفی که با مواردی مانند Dispatchers.IO یا Dispatchers.Default تعیین شده‌اند، اجرا کنید.در اینجا یک مثال استفاده از flowOn برای اجرای یک Flow بر روی ترد  (IO) نشان داده شده است:fun main() = runBlocking {    val flow = flow {        (1..5).forEach {            println(&quot;Emitting $it in thread ${Thread.currentThread().name}&quot;)            emit(it)        }    }    flowOn(Dispatchers.IO)        .collect {            println(&quot;Collected $it in thread ${Thread.currentThread().name}&quot;)        }}این کد یک Flow را ایجاد می‌کند که اعداد 1 تا 5 را تولید می‌کند و سپس از flowOn استفاده می‌کند تا اجرای Flow را بر روی ترد (IO) فراهم کند. وقتی این کد اجرا می‌شود، شما ممکن است یک خروجی مشابه با این را ببینید:Emitting 1 in thread main @coroutine#1Collected 1 in thread DefaultDispatcher-worker-1 @coroutine#2Emitting 2 in thread main @coroutine#1Collected 2 in thread DefaultDispatcher-worker-1 @coroutine#3Emitting 3 in thread main @coroutine#1Collected 3 in thread DefaultDispatcher-worker-1 @coroutine#4Emitting 4 in thread main @coroutine#1Collected 4 in thread DefaultDispatcher-worker-1 @coroutine#5Emitting 5 in thread main @coroutine#1Collected 5 in thread DefaultDispatcher-worker-1 @coroutine#6همانطور که می‌بینید، این Flow بر روی ترد  (IO) اجرا می‌شود که با نام DefaultDispatcher-worker-1 شناخته می‌شود.حالا بریم مثال های بیشتری بزنیم تا مفهوم flow و کار کردن با اونها بیشتر برامون جا بیوفتهوقتی یک فلو روی ترد IO (Input/Output) اجرا می‌شود و سپس در داخل یک کوروتین که با Dispatcher.Main اجرا می‌شود، نتیجه این است که عملیاتی که باید در ترد IO انجام می‌شد، از ترد IO به ترد اصلی (Main Thread) منتقل می‌شود. این اتفاق به دلیل استفاده از flowOn در ابتدای فرآیند وقوع می‌پذیرد، که باعث می‌شود فلو روی یک ترد دیگری اجرا شود.حالا بیایید یک مثال را بررسی کنیم:fun main() {    runBlocking {        val job = CoroutineScope(Dispatchers.Main).launch {            // Launch a coroutine on the Main thread            val flowResult = withContext(Dispatchers.IO) {                // Switch to the IO thread to run the flow                createFlow()                    .map { it * it }                    .toList() // Collect the flow to a list            }            println(&quot;Result on Main thread: $flowResult&quot;)        }        job.join() // Wait for the coroutine to finish    }}fun createFlow(): Flow&lt;Int&gt; = flow {    // Emit some numbers    emit(1)    emit(2)    emit(3)}در این مثال، یک کوروتین در ترد اصلی (Main Thread) ایجاد می‌شود. درون این کوروتین، با استفاده از withContext(Dispatchers.IO)، عملیات ایجاد و اجرای فلو بر روی ترد IO صورت می‌گیرد. سپس نتایج فلو (مربوط به محاسبات مربوط به مترهای فلو) به ترد اصلی بازگردانده می‌شود و در نهایت چاپ می‌شود.خروجی این کد  به شکل زیر میباشد:Result on Main thread: [1, 4, 9]الان چند مثال بیشتری برایتان خواهم زد که از حالت‌های مختلفی از flowOn و استفاده از Flow در کوروتین استفاده می‌کنند.مثال ۲: استفاده از flowOn برای اجرای یک فلو بر روی ترد پیش‌فرض (Default)fun main() = runBlocking {    val job = CoroutineScope(Dispatchers.Main).launch {        val result = createFlow()            .flowOn(Dispatchers.Default)            .map { it * it }            .toList()        println(&quot;Result on Main thread: $result&quot;)    }    job.join()}fun createFlow(): Flow&lt;Int&gt; = flow {    emit(1)    emit(2)    emit(3)}حتی میتوانیم  از flowOn برای اجرای یک فلو بر روی یک ترد خاص خود استفاده کنیم .به صورت زیر:fun main() = runBlocking {    val customDispatcher = newSingleThreadContext(&quot;CustomThread&quot;)    val job = CoroutineScope(Dispatchers.Main).launch {        val result = createFlow()            .flowOn(customDispatcher)            .map { it * it }            .toList()        println(&quot;Result on Main thread: $result&quot;)    }    job.join()}fun createFlow(): Flow&lt;Int&gt; = flow {    emit(1)    emit(2)    emit(3)}خروجی کد بالا به صورت زیر میباشد : Result on Main thread: [1, 4, 9]در این مثال‌ها، از flowOn برای تعیین تردی که یک فلو بر روی آن اجرا خواهد شد استفاده شده است. با تغییر Dispatcher مربوطه در flowOn، می‌توانید فلو را بر روی تردهای مختلفی اجرا کنید.اگر در یک فلو flowOn مشخص نشود، ترد پیشفرض برای اجرای فلو تردی است که برنامه اصلی در آن اجرا می‌شود. این به طور پیش‌فرض معمولاً ترد اصلی یا Main Thread است. در برنامه‌های کاتلین کروتین، تردهای پیش‌فرض برای اجرای کدها که بدون هیچگونه مشخصات خاصی فراخوانی می‌شوند، تردهایی هستند که از Dispatchers.Main استفاده می‌کنند.به عبارت دیگر، اگر شما flowOn را بر روی یک فلو ناشناخته اجرا کنید، فلو بر روی ترد اصلی (Main Thread) اجرا می‌شود. این رفتار به طور پیش‌فرض در کروتین و در فلوهای بدون flowOn صورت می‌گیرد.به عنوان مثال:fun main() = runBlocking {    val job = CoroutineScope(Dispatchers.Main).launch {        val result = createFlow()            .map { it * it }            .toList()        println(&quot;Result on Main thread: $result&quot;)    }    job.join()}fun createFlow(): Flow&lt;Int&gt; = flow {    emit(1)    emit(2)    emit(3)}در این مثال، اگرچه flowOn مشخص نشده است، اما فلو بر روی ترد اصلی (Main Thread) اجرا می‌شود.در این مثال، هیچ تردی برای فلو و کوروتین مشخص نشده است. فلو بصورت پیش‌فرض بر روی ترد اصلی اجرا می‌شود و کوروتین هم بصورت پیش‌فرض بر روی ترد پشته‌ای (Default Dispatcher) اجرا می‌شود:fun main() = runBlocking {    val job = launch {        val result = createFlow()            .map { it * it }            .toList()        println(&quot;Result on Default thread: $result&quot;)    }    job.join()}fun createFlow(): Flow&lt;Int&gt; = flow {    emit(1)    emit(2)    emit(3)}خروجی:Result on Default thread: [1, 4, 9]در این مثال، همچنین فلو بصورت پیش‌فرض بر روی ترد اصلی (Main Thread) اجرا می‌شود، اما اجرای کوروتین بصورت پیش‌فرض بر روی ترد پشته‌ای (Default Dispatcher) انجام می‌شود.این دیسپچر یک thread pull  با اندازه‌ای برابر با تعداد هسته‌های موجود در دستگاهی که کد شما در آن اجرا می‌شود دارد (اما حداقل دو ترد دارد).آشنایی با Flow Builder ها در کاتلینآشنایی با Terminal Operators در flow هاآشنایی با Hot Flow و Cold Flow آشنایی با StateFlow و SharedFlow&lt;br/&gt;</description>
                <category>narjes Mansoori</category>
                <author>narjes Mansoori</author>
                <pubDate>Wed, 28 Feb 2024 13:59:26 +0330</pubDate>
            </item>
                    <item>
                <title>منظور از version catalog در اندروید چیست؟</title>
                <link>https://virgool.io/@narjes.mansoori/%D9%85%D9%86%D8%B8%D9%88%D8%B1-%D8%A7%D8%B2-version-catalog-%D8%AF%D8%B1-%D8%A7%D9%86%D8%AF%D8%B1%D9%88%DB%8C%D8%AF-%DA%86%DB%8C%D8%B3%D8%AA-slddlgexyfgl</link>
                <description>در اندروید، وقتی که شما از کتابخانه‌ها و ابزارهای مختلفی برای توسعه برنامه استفاده می‌کنید، برخی از این کتابخانه‌ها وابستگی‌ها (dependencies) را دارند که می‌توانند به صورت منظم به روز رسانی شوند. در برنامه‌های اندروید، مدیریت این وابستگی‌ها و نگهداری نسخه‌های آنها مهم است. اینجاست که مفهوم &quot;version catalog&quot; (فهرست نسخه‌ها) به کار می‌آید.در واقع Version catalog در اندروید یک فایل است که شما می‌توانید در آن نسخه‌های وابستگی‌های مورد استفاده در پروژه‌ی خود را مشخص کنید. این فایل به شما امکان می‌دهد تا نسخه‌های وابستگی‌ها را در یک مکان مرکزی تعریف کنید و از آن در تمامی ماژول‌ها و زیرپروژه‌های پروژه‌ی خود استفاده کنید. این کار به شما امکان می‌دهد تا مدیریت نسخه‌ها را بهبود بخشید و بهره‌وری و انعطاف پذیری را در توسعه نرم‌افزار افزایش دهید.فایل version catalog معمولاً با نامی مانند versions.gradle یا versions.properties در پروژه ایجاد می‌شود. در این فایل، شما می‌توانید نسخه‌های وابستگی‌های مختلف را به صورت زیر تعریف کنید:ext {
    // Define dependency versions
    appCompatVersion = &amp;quot1.3.0&amp;quot
    retrofitVersion = &amp;quot2.9.0&amp;quot
    // and so on...
}سپس، در فایل build.gradle یا build.gradle.kts مربوط به هر ماژول یا زیرپروژه، می‌توانید به این نسخه‌ها ارجاع دهید، به این ترتیب که مقادیر معرفی شده در فایل version catalog را فراخوانی کنید.به عنوان مثال، در یک فایل build.gradle:dependencies {
    implementation &amp;quotandroidx.appcompat:appcompat:${appCompatVersion}&amp;quot
    implementation &amp;quotcom.squareup.retrofit2:retrofit:${retrofitVersion}&amp;quot
    // and so on...
}با این کار، وقتی که نیاز به به روز رسانی نسخه‌ها دارید، تنها کافی است که آنها را در فایل version catalog تغییر دهید و تمامی ماژول‌ها از آن نسخه‌ها استفاده می‌کنند.استفاده از version catalog در اندروید به شما کمک می‌کند تا مدیریت و نگهداری نسخه‌های وابستگی‌ها را ساده‌تر و کارآمدتر کنید و از ابهامات و سردرگمی‌های مربوط به نسخه‌ها جلوگیری کنید.امیدوارم این پست مفید واقع باشه براتون 😊</description>
                <category>narjes Mansoori</category>
                <author>narjes Mansoori</author>
                <pubDate>Wed, 28 Feb 2024 11:29:19 +0330</pubDate>
            </item>
                    <item>
                <title>آشنایی کامل با مفاهیم supervisorScope و supervisorJob در کوروتین ها کوروتین ها</title>
                <link>https://virgool.io/@narjes.mansoori/%D8%A2%D8%B4%D9%86%D8%A7%DB%8C%DB%8C-%D8%A8%D8%A7-%D9%85%D9%81%D8%A7%D9%87%DB%8C%D9%85-supervisorscope-%D9%88-supervisorjob-%D8%AF%D8%B1-%DA%A9%D9%88%D8%B1%D9%88%D8%AA%DB%8C%D9%86-%D9%87%D8%A7-o5jilijxjfxq</link>
                <description>خب در پست قبلی در مورد مدیریت exception ها در کوروتین ها صحبت کردیم .حالا سوالی مطرح بود که اگر در حین اجرای کوروتین ها برای هر کوروتین خطایی اتفاق بیوفتد وضعیت بقیه کوروتین ها به چه صورت میباشد؟پس لازم هست اینجا با مفاهیم   supervisorScope و  supervisorJob آشنا شویم .الف-:coroutineScope یک suspend function است که یک بلاک کد را به عنوان پارامتر می‌گیرد و یک کوروتین را ایجاد می‌کند.خب در مورد coroutineScope قبلا هم صحبت کرده بودیم .بنابرین وظیفه‌ی coroutineScope این است که تا زمانی که کلیه‌ی کوروتین‌های داخلی اجرا نشده‌اند، اجازه نمی‌دهد که خروجی داده شود.اگر یکی از کوروتین‌های داخلی با خطا روبرو شود، تمامی کوروتین‌های دیگر در coroutineScope لغو می‌شوند و این کوروتین همچنین با یک CancellationException کنسل می‌شود.ب- :supervisorScope نیز مانند coroutineScope یک suspend function است که یک بلاک کد را به عنوان پارامتر می‌گیرد و یک کوروتین را ایجاد می‌کند. با این تفاوت که supervisorScope اجازه می‌دهد که کوروتین‌های داخلی به طور مستقل از یکدیگر اجرا شوند. به این معنی که اگر یکی از کوروتین‌های داخلی با خطا مواجه شود، فقط آن کوروتین لغو می‌شود و بقیه ادامه می‌یابند.با استفاده از این دو تابع، می‌توانید رفتار مناسبی برای مدیریت خطاها و اجرای کوروتین‌ها در برنامه‌ی خود ایجاد کنید.خب حالا مثال بزنیم که بیشتر درک کنیم : ابتدا  فرض کنید می‌خواهید یک بلاک کد را با استفاده از کوروتین‌ها اجرا کنید و اطمینان حاصل کنید که همه‌ی کوروتین‌ها با خطا مواجه نشده‌اند. ابتدا، با استفاده از coroutineScope این کار را انجام می‌دهیم:suspend fun main() {    try {        coroutineScope {            val job1 = launch {                delay(1000)                println(&quot;Task 1 completed&quot;)            }            val job2 = launch {                delay(2000)                println(&quot;Task 2 completed&quot;)            }            // Wait for all child coroutines to complete            job1.join()            job2.join()            println(&quot;All tasks completed successfully&quot;)        }    } catch (e: Exception) {        println(&quot;An error occurred: ${e.message}&quot;)    }}خروجی کد بالا به صورت زیر میباشد : Task 1 completedTask 2 completedAll tasks completed successfullyدر این کد، دو کوروتین (job1 و job2) در یک coroutineScope اجرا می‌شوند. ابتدا کوروتین job1 با یک تأخیر 1000 میلی‌ثانیه اجرا شده و پس از آن پیام &quot;Task 1 completed&quot; را چاپ می‌کند. سپس کوروتین job2 با یک تأخیر 2000 میلی‌ثانیه اجرا شده و پس از آن پیام &quot;Task 2 completed&quot; را چاپ می‌کند. سپس با استفاده از join() انتظار می‌رود تا هر دو کوروتین به پایان برسند و پیام &quot;All tasks completed successfully&quot; چاپ می‌شود. حالا اگر یکی از کوروتین‌ها با خطا مواجه شود، کل بلاک کد coroutineScope به طور کامل لغو می‌شود:suspend fun main() {    try {        coroutineScope {            val job1 = launch {                delay(1000)                println(&quot;Task 1 completed&quot;)            }            val job2 = launch {                delay(2000)                println(&quot;Task 2 completed&quot;)                throw Exception(&quot;Error in task 2&quot;)            }            // Wait for all child coroutines to complete            job1.join()            job2.join()            println(&quot;All tasks completed successfully&quot;)        }    } catch (e: Exception) {        println(&quot;An error occurred: ${e.message}&quot;)    }}حالا خروجی کد زیر به صورت زیر است :Task 1 completedTask 2 completedAn error occurred: Error in task 2در این کد، دو کوروتین (job1 و job2) در یک coroutineScope اجرا می‌شوند. ابتدا کوروتین job1 با یک تأخیر 1000 میلی‌ثانیه اجرا شده و پس از آن پیام &quot;Task 1 completed&quot; را چاپ می‌کند. سپس کوروتین job2 با یک تأخیر 2000 میلی‌ثانیه اجرا شده و پیام &quot;Task 2 completed&quot; را چاپ می‌کند. اما سپس یک استثناء (Exception) در job2 پرتاب می‌شود با پیام &quot;Error in task 2&quot;.زمانی که استثناء در job2 پرتاب می‌شود، اجرای کد از داخل coroutineScope خارج شده و به بلاک catch که در main تعریف شده است منتقل می‌شود. از طریق این بلاک catch، پیام &quot;An error occurred: Error in task 2&quot; چاپ می‌شود.حالا اگر می‌خواهید تنها کوروتینی که با خطا مواجه شده است را لغو کنید و سایر کوروتین‌ها ادامه دهند، از supervisorScope استفاده کنید:suspend fun main() {    try {        supervisorScope {            val job1 = launch {                delay(1000)                println(&quot;Task 1 completed&quot;)            }            val job2 = launch {                delay(2000)                println(&quot;Task 2 completed&quot;)                throw Exception(&quot;Error in task 2&quot;)            }            // Wait for all child coroutines to complete            job1.join()            job2.join()            println(&quot;All tasks completed successfully&quot;)        }    } catch (e: Exception) {        println(&quot;An error occurred: ${e.message}&quot;)    }}خروجی کد بالا به صورت زیر است :Task 1 completedTask 2 completedAll tasks completed successfullyدر این کد، دو کوروتین (job1 و job2) در یک supervisorScope اجرا می‌شوند. ابتدا کوروتین job1 با یک تأخیر 1000 میلی‌ثانیه اجرا شده و پس از آن پیام &quot;Task 1 completed&quot; را چاپ می‌کند. سپس کوروتین job2 با یک تأخیر 2000 میلی‌ثانیه اجرا شده و پیام &quot;Task 2 completed&quot; را چاپ می‌کند. اما سپس یک استثناء (Exception) در job2 پرتاب می‌شود با پیام &quot;Error in task 2&quot;.با استفاده از supervisorScope، خطاهای مربوط به کوروتین‌های فرزند به کوروتین والد منتقل نمی‌شوند. بنابراین، بلاک catch در main فراخوانی نخواهد شد و برنامه به اجرای خود ادامه می‌دهد.خب حالا ببینیم SupervisorJob چی هست !!خب Job یک مفهوم اصلی در کوروتین‌ها است و یک نشانگر برای یک کار مشخص است.هنگامی که شما یک کوروتین را ایجاد می‌کنید، یک Job به عنوان نتیجه برگردانده می‌شود که می‌توانید از آن برای لغو کردن کار، پیگیری وضعیت آن، و غیره استفاده کنید.حالا SupervisorJob یک زیرکلاس از Job است که برای ایجاد یک محیط کاری (supervisor scope) استفاده می‌شود که کوروتین‌های مستقل از یکدیگر راه اندازی می‌شوند.در یک SupervisorJob، اگر یکی از کوروتین‌ها با خطا مواجه شود، فقط آن کوروتین لغو می‌شود و سایر کوروتین‌ها ادامه می‌یابند.حالا با استفاده از مثال، این تفاوت را بیان می‌کنیم:suspend fun main() {    try {        val parentJob = Job() // Creating a parent Job        val child1 = CoroutineScope(parentJob).launch {            delay(1000)            println(&quot;Task 1 completed&quot;)        }        val child2 = CoroutineScope(parentJob).launch {            delay(2000)            println(&quot;Task 2 completed&quot;)            throw Exception(&quot;Error in task 2&quot;)        }        // Wait for all child coroutines to complete        parentJob.join()        println(&quot;All tasks completed successfully&quot;)    } catch (e: Exception) {        println(&quot;An error occurred: ${e.message}&quot;)    }}در این مثال، هر دو کوروتین child1 و child2 به عنوان فرزندان یک Job ایجاد شده‌اند. بنابراین، اگر یکی از آنها با خطا مواجه شود، تمام کوروتین‌ها در parentJob لغو می‌شوند.هر دو کروتین یک تاخیر دارند و پس از اتمام تاخیر، پیامی را چاپ می‌کنند. با توجه به این که کروتین دوم یک استثناء را پرتاب می‌کند، برنامه خطا خواهد داشت و عبارت &quot;An error occurred: Error in task 2&quot; را چاپ می‌کند.حالا همین کد را با استفاده از SupervisorJob اصلاح می‌کنیم:suspend fun main() {    try {        val parentJob = SupervisorJob() // Creating a parent SupervisorJob        val child1 = CoroutineScope(parentJob).launch {            delay(1000)            println(&quot;Task 1 completed&quot;)        }        val child2 = CoroutineScope(parentJob).launch {            delay(2000)            println(&quot;Task 2 completed&quot;)            throw Exception(&quot;Error in task 2&quot;)        }        // Wait for all child coroutines to complete        parentJob.join()        println(&quot;All tasks completed successfully&quot;)    } catch (e: Exception) {        println(&quot;An error occurred: ${e.message}&quot;)    }}در این‌جا از SupervisorJob به جای Job برای ایجاد والد استفاده شده است.وظیفه SupervisorJob تفاوتی با Job دارد. وقتی که یک کروتین کودک در یک SupervisorJob شکست می‌خورد (مثل پرتاب یک استثناء)، تنها کروتین متاثر می‌شود و کروتین‌های دیگر تحت مدیریت همچنان ادامه می‌یابند. بنابراین، در اینجا کروتین دوم یک استثناء را پرتاب می‌کند، اما کروتین اول همچنان ادامه دارد. پس از اتمام هر دو کروتین، پیام &quot;All tasks completed successfully&quot; چاپ می‌شود.بنابراین، خروجی برنامه به صورت زیر خواهد بود:Task 1 completedTask 2 completedAll tasks completed successfully</description>
                <category>narjes Mansoori</category>
                <author>narjes Mansoori</author>
                <pubDate>Tue, 27 Feb 2024 16:43:51 +0330</pubDate>
            </item>
                    <item>
                <title>مفهوم Exception Handling در کوروتین ها</title>
                <link>https://virgool.io/@narjes.mansoori/%D9%85%D9%81%D9%87%D9%88%D9%85-exception-handling-%D8%AF%D8%B1-%DA%A9%D9%88%D8%B1%D9%88%D8%AA%DB%8C%D9%86-%D9%87%D8%A7-xt0pgjtpv2lv</link>
                <description>در Coroutines، مدیریت استثناء (Exception Handling) یک موضوع مهمه که باید آن را یاد بگیریم. خب حالا در اینجا به برخی روش‌های مدیریت استثناء در زمان استفاده از launch و async می‌پردازیم.سعی میکنیم با مثال پیش بریم که درک بیشتری از مطالب داشته باشیم.زمان استفاده از launchزمانی که از launch استفاده می‌کنیم، می‌توانیم با استفاده از بلاک try-catch یا یک exception handler خطا را مدیریت کنیم.استفاده از بلاک try-catch:GlobalScope.launch(Dispatchers.Main) {    try {        fetchUserAndSaveInDatabase()     } catch (exception: Exception) {        Log.d(TAG, &quot;$exception handled !&quot;)    }}در مثال بالا اگر خطایی در fetchUserAndSaveInDatabase رخ دهد در بلاک catch  مدریت میشود.خب حالا اگر نخواهیم از try - catch استفاده کنیم میتوانیم به صورت زیر از CoroutineExceptionHandler  استفاده کنیم .استفاده از Exception Handler:ابتدا باید یک exception handler ایجاد کنیم:val handler = CoroutineExceptionHandler { _, exception -&gt;    Log.d(TAG, &quot;$exception handled !&quot;)}سپس می‌توانیم handler را به این صورت به launch پاس دهیم:kotlinCopy codeGlobalScope.launch(Dispatchers.Main + handler) {    fetchUserAndSaveInDatabase()}زمان استفاده از asyncزمان استفاده از async نیز باید با استفاده از بلاک try-catch خطا را مدیریت کنیم.val deferredUser = GlobalScope.async {    fetchUser()}try {    val user = deferredUser.await()} catch (exception: Exception) {    Log.d(TAG, &quot;$exception handled !&quot;)}در موارد استفاده واقعی، ممکن است بخواهیم با شرایط خاصی روبه‌رو شویم. به عنوان مثال، اگر بخواهیم در صورت خطا یک لیست خالی را برگردانیم و با پاسخ دیگری ادامه دهیم، می‌توانیم بلاک try-catch را برای هر تابع فراخوانی شده اعمال کنیم.kotlinCopy codelaunch {    val users = try {        getUsers()    } catch (e: Exception) {        emptyList&lt;User&gt;()    }    val moreUsers = try {        getMoreUsers()    } catch (e: Exception) {        emptyList&lt;User&gt;()    }}خب حالا قطعه کد بالا مشکل ما رو حل کرد اما ممکن هست بخواهیم تمامی توابع را همزمان اجرا کنیم، باید از coroutineScope به صورت زیر استفاده کنیم:launch {    try {        coroutineScope {            val usersDeferred = async {  getUsers() }            val moreUsersDeferred = async { getMoreUsers() }            val users = usersDeferred.await()            val moreUsers = moreUsersDeferred.await()        }    } catch (exception: Exception) {        Log.d(TAG, &quot;$exception handled !&quot;)    }}خب در کد بالا اگر یکی از دورخواست ها به خطا بخورد ما وارد بلاک catch میشویم و دیگه بقیه درخواست های ما اجرا نخوهد شد ولی  اگر بخواهیم در صورت خطا درخواست های دیگه ادامه داده شوند و باز هم با پاسخ‌های دیگر ادامه دهیم و تمامی توابع را همزمان اجرا کنیم، باید از supervisorScope استفاده کنیم.به مثال زیر توجه کنید:kotlinCopy codelaunch {    supervisorScope {        val usersDeferred = async { getUsers() }        val moreUsersDeferred = async { getMoreUsers() }        val users = try {            usersDeferred.await()        } catch (e: Exception) {            emptyList&lt;User&gt;()        }        val moreUsers = try {            moreUsersDeferred.await()        } catch (e: Exception) {            emptyList&lt;User&gt;()        }    }}نتیجه‌گیریدر زمان استفاده از launch می‌توانیم با استفاده از try-catch یا CoroutineExceptionHandler به مدیریت استثناء بپردازیم.در زمان استفاده از async، علاوه بر try-catch، دو گزینه برای مدیریت همزمانی داریم: coroutineScope و supervisorScope.با استفاده از supervisorScope، در صورت خطا اجرای سایر وظایف ادامه خواهد داشت، در حالی که coroutineScope وقتی یکی از وظایف آن خطا داشته باشد، کل وظایف را لغو می‌کند.آشنایی کامل با مفاهیم supervisorScope و supervisorJob در کوروتین ها</description>
                <category>narjes Mansoori</category>
                <author>narjes Mansoori</author>
                <pubDate>Mon, 26 Feb 2024 00:24:52 +0330</pubDate>
            </item>
                    <item>
                <title>آشنایی با مفاهیم Coroutine dispatcher,Coroutine builder, Coroutine Scope</title>
                <link>https://virgool.io/@narjes.mansoori/%D8%A2%D8%B4%D9%86%D8%A7%DB%8C%DB%8C-%D8%A8%D8%A7-%D9%85%D9%81%D8%A7%D9%87%DB%8C%D9%85-coroutine-dispatchercoroutine-builder-coroutine-scope-bwufsxxnt5un</link>
                <description>سلام بچه ها امیدوارم حالتون خوب باشهخب باز اومدیم با یه پست دیگه از ادامه پست های مربوط به کوروتین.خب کوروتین بیلدرها ، توابعی هستند که به شما کمک می‌کنند کوروتین‌ها را به سادگی ایجاد و مدیریت کنید. در واقع، آن‌ها مبدل‌هایی هستند که کار ساخت و اجرای کوروتین‌ها را برای شما آسان‌تر می‌کنند.نمونه‌هایی از کوروتین بیلدرها عبارتند از:1. launch با استفاده از این بیلدر می‌توانید یک کوروتین بدون نیاز به نتیجه بازگشتی ایجاد کنید. به عبارت دیگر، اجرای کوروتین با launch به صورت آسینکرون صورت می‌گیرد و برنامه می‌تواند بلافاصله پس از راه‌اندازی کوروتین به اجرای خود ادامه دهد.2. async با استفاده از این بیلدر، می‌توانید یک کوروتین با نیاز به نتیجه بازگشتی ایجاد کنید. کوروتین‌های ساخته شده با async می‌توانند یک مقدار نتیجه را برگردانند که با استفاده از تابع await قابل دریافت است.3. runBlocking این بیلدر برای اجرای یک کوروتین در یک بلاک blocking استفاده می‌شود. به عبارت دیگر، با استفاده از runBlocking می‌توانید در توابع blocking مانند تابع main از کوروتین استفاده کنید.بهتر است با یک مثال عملی کوروتین بیلدرها را توضیح دهم تا بهتر براتون جا بیوفته. در اینجا، یک برنامه ساده را در نظر بگیرید که از دو کوروتین برای اجرای دو تسک مستقل استفاده می‌کند. برای این منظور، از کوروتین بیلدرها launch و async استفاده خواهیم کرد.fun main() {// ایجاد یک کوروتین با استفاده از launchval job1 = CoroutineScope(Dispatchers.Default).launch {println(&quot;شروع کوروتین 1&quot;)delay(1000)println(&quot;پایان کوروتین 1&quot;)}// ایجاد یک کوروتین با استفاده از asyncval deferred = CoroutineScope(Dispatchers.Default).async {println(&quot;شروع کوروتین 2&quot;)delay(2000)println(&quot;پایان کوروتین 2&quot;)&quot;مقدار بازگشتی از کوروتین 2&quot;}// انتظار برای پایان اجرای کوروتین 2 و دریافت نتیجه آنrunBlocking {val result = deferred.await()println(&quot;نتیجه کوروتین 2: $result&quot;)}println(&quot;پایان برنامه&quot;)}در این مثال، ابتدا یک کوروتین با استفاده از launch ساخته شده است. این کوروتین پس از یک تاخیر ۱ ثانیه‌ای، یک پیام چاپ می‌کند و به پایان می‌رسد.سپس با استفاده از async، یک کوروتین دیگر ساخته می‌شود. این کوروتین نیز پس از یک تاخیر ۲ ثانیه‌ای، یک پیام چاپ کرده و مقدار &quot;مقدار بازگشتی از کوروتین ۲&quot; را به عنوان نتیجه خروجی تولید می‌کند.در نهایت، تابع runBlocking برای انتظار و دریافت نتیجه اجرای کوروتین ۲ استفاده می‌شود. سپس پیامی که نتیجه کوروتین ۲ را نشان می‌دهد چاپ می‌شود.در انتها، پیام &quot;پایان برنامه&quot; چاپ می‌شود.این مثال نشان می‌دهد که با استفاده از کوروتین بیلدرها launch و async، می‌توانید به راحتی کوروتین‌ها را ایجاد و مدیریت کنید. همچنین، با استفاده از await می‌توانید نتیجه یک کوروتین با نیاز به خروجی را دریافت کنید و در سایر بخش‌های برنامه خود استفاده کنید.در واقع runBlocking یک کوروتین بیلدر در Kotlin است که به شما اجازه می‌دهد یک بلاک blockingرا اجرا کنید و در عین حال از کوروتین‌ها استفاده کنید.وقتی از runBlocking استفاده می‌کنید، برنامه شما متوقف می‌شود تا تمامی کوروتین‌ها در بلاک runBlocking اجرا و تکمیل شوند. به عبارت دیگر، runBlocking مانع از اجرای بقیه کد بعد از این بلاک می‌شود تا تمامی کوروتین‌ها به پایان برسند.در اینجا یک مثال ساده را در نظر بگیرید:fun main() {println(&quot;شروع برنامه&quot;)runBlocking {launch {delay(1000)println(&quot;کوروتین 1&quot;)}launch {delay(2000)println(&quot;کوروتین 2&quot;)}println(&quot;کد درون بلاک runBlocking&quot;)}println(&quot;پایان برنامه&quot;)}در این مثال، ما از runBlocking برای اجرای دو کوروتین استفاده کرده‌ایم. هر کدام از این کوروتین‌ها دارای یک تاخیر هستند و پس از آن یک پیام چاپ می‌کنند.وقتی برنامه اجرا می‌شود، ابتدا پیام &quot;شروع برنامه&quot; چاپ می‌شود. سپس بلاک runBlocking شروع می‌شود. درون این بلاک، دو کوروتین با استفاده از launch ایجاد شده‌اند. اما زمانی که به سطر runBlocking می‌رسیم، برنامه متوقف می‌شود تا تمامی کوروتین‌ها درون این بلاک به پایان برسند.در این مثال، هر کوروتین یک تاخیر مختلف دارد. کوروتین اول پس از ۱ ثانیه چاپ می‌کند و کوروتین دوم پس از ۲ ثانیه چاپ می‌کند. سپس پیام &quot;کد درون بلاک runBlocking&quot; چاپ می‌شود. بعد از اتمام بلاک runBlocking، برنامه از حالت متوقف خارج می‌شود و پیام &quot;پایان برنامه&quot; چاپ می‌شود.خب حالا بریم یه مثال دیگه بزنیم تا بازم بیشتر مطالب بالا رو درک کنیم.فرض کنید دو تسک طولانی مدت داریم به نام‌های doLongRunningTaskOne و doLongRunningTaskTwo که هرکدام یک نتیجه از نوع عدد صحیح برمی‌گردانند.private suspend fun doLongRunningTaskOne(): Int {return withContext(Dispatchers.Default) {// کد مربوط به وظیفه طولانی مدت اول// تاخیر مصنوعی برای شبیه‌سازیdelay(2000)return@withContext 10}}private suspend fun doLongRunningTaskTwo(): Int {return withContext(Dispatchers.Default) {// کد مربوط به وظیفه طولانی مدت دوم// تاخیر مصنوعی برای شبیه‌سازیdelay(2000)return@withContext 10}}حالا بیایید مثالی از استفاده از این دو تابع ببینیم. در این مثال، ما می‌خواهیم هر دو تسک را اجرا کرده و مجموع نتایج آن‌ها را نمایش دهیم.GlobalScope.launch(Dispatchers.Main) {val resultOne = doLongRunningTaskOne()val resultTwo = doLongRunningTaskTwo()showResult(resultOne + resultTwo) // اجرا در نخ رابط کاربری (UI)}در این کد، با استفاده از GlobalScope.launch و با استفاده از Dispatchers.Main  در ترد اصلی یک کوروتین ایجاد می‌کنیم. ما به ترتیب تابع doLongRunningTaskOne و سپس doLongRunningTaskTwo را فراخوانی می‌کنیم و انتظار می‌کشیم تا هر تسک به ترتیب خود به پایان برسد. در نهایت، ما مجموع نتایج را بعد از 4000 میلی ثانیه در ترد رابط کاربری یا ترد اصلی  نمایش می‌دهیم.اما اگر می‌خواهیم هر دو تسک را به صورت همزمان اجرا کنیم، از async استفاده می‌کنیم. زیرا نیاز داریم به نتیجه این وظیفه‌ها دسترسی داشته باشیم.GlobalScope.launch {val deferredOne = async {doLongRunningTaskOne()}val deferredTwo = async {doLongRunningTaskTwo()}val result = deferredOne.await() + deferredTwo.await()showResult(result) // اجرا در نخ رابط کاربری (UI)}در این کد، دو کوروتین را با استفاده از async ایجاد می‌کنیم. هر کدام از این کوروتین‌ها نماینده یک وظیفه طولانی مدت هستند. با استفاده از async، هر دو وظیفه به صورت همزمان اجرا می‌شوند. ما با استفاده از deferredOne.await و deferredTwo.await منتظر نتایج هر وظیفه می‌مانیم و سپس به محاسبه جمع آن‌ها می‌پردازیم. در نهایت، نتیجه را بعد از 2000 میلی ثانیه در نخ رابط کاربری نمایش می‌دهیم.خلاصه‌اش:در واقع launch و async هر دو برای ایجاد کوروتین‌ها استفاده می‌شوند و امکان اجرای همزمان وظایف را      فراهم می‌کنند.از async زمانی استفاده می‌شود که نتیجه اجرای یک کوروتین را نیاز داریم دریافت کنیم، در حالی که launch زمانی استفاده می‌شود که نیازی به نتیجه نداشته باشیم.در واقع withContext یک تابع تعلیقی است که برای تغییر ترد اجرای کوروتین فعلی استفاده می‌شود و هیچ کوروتین جدیدی را راه‌اندازی نمی‌کند.خب بیشتر بهت توضیح میدم نگران نباش.😊پس withContext یک تابع تعلیقی در کتابخانه Coroutines است که برای تغییر محیط اجرای کوروتین استفاده می‌شود. به صورت ساده، می‌توانیم آن را برای تغییرdispatcher (محیط اجرا) کوروتین استفاده کنیم.بیایید با یک مثال ساده این را بررسی کنیم. فرض کنید که ما دو تابع داریم: fetchDataFromNetwork که اطلاعات را از شبکه دریافت می‌کند و parseData که اطلاعات دریافت شده را پردازش می‌کند. فرض کنید هر کدام از این توابع زمان زیادی برای اجرا نیاز دارند.suspend fun fetchDataFromNetwork(): String {return withContext(Dispatchers.IO) {// کد مربوط به دریافت اطلاعات از شبکه}}suspend fun parseData(data: String): List&lt;String&gt; {return withContext(Dispatchers.Default) {// کد مربوط به پردازش اطلاعات}}در این مثال، fetchDataFromNetwork با استفاده از withContext محیط اجرای کوروتین خود را به Dispatchers.IO تغییر می‌دهد. این به این معنی است که این تابع برای اجرا از نخ‌های I/O استفاده می‌کند که به طور خاص برای عملیات ورودی/خروجی شبکه مناسب هستند.همچنین، parseDataبا استفاده از withContext محیط اجرای کوروتین خود را به Dispatchers.Default تغییر می‌دهد. این به این معنی است که این تابع برای اجرا از نخ‌های پیش‌فرض استفاده می‌کند که برای عملیات محاسباتی مناسب هستند.پس در نتیجه با استفاده از withContext، ما می‌توانیم محیط اجرای کوروتین را به دلخواه خود تغییر دهیم و کد را در محیط مناسب اجرا کنیم.امیدوارم دیگه این توضیحات به شما در درک استفاده از withContext کمک کند.خب حالا بریم ببینیم Scope ها در کوروتین چیا هستند و قرار هست چیکار کنند!!!🤔در کتابخانه کوروتین ، چندین نوع CoroutineScope وجود دارد که برای مدیریت کوروتین‌ها در محدوده(LifeCycle) مختلف استفاده می‌شوند. در زیر به برخی از انواع اصلی CoroutineScopeاشاره می‌کنم و هرکدام را با مثال توضیح می‌دهم:GlobalScope:این GlobalScope یک CoroutineScope است که در سراسر برنامه قابل دسترسی است و از طریق آن می‌توانید کوروتین‌ها را ایجاد و مدیریت کنید. این نوع CoroutineScope در برنامه‌های کوچک و ساده معمولاً مورد استفاده قرار می‌گیرد. اما بهتر است از استفاده از GlobalScope خودداری کنید و از ایجاد CoroutineScope محدودتر در محدوده مورد نظر استفاده کنید.بیایید با هم یه مثال ببینیم :fun main() {println(&quot;شروع برنامه&quot;)// ایجاد یک کوروتین در GlobalScopeGlobalScope.launch {delay(1000)println(&quot;کوروتین&quot;)}println(&quot;پایان برنامه&quot;)}CoroutineScope:شما می‌توانید یک Scopeمحدود بر اساس یک منطقه خاص در برنامه‌تان ایجاد کنید. این نوع CoroutineScope معمولاً برای محدوده‌های مشخص در برنامه مفید است و به شما امکان می‌دهد که کوروتین‌ها را در این محدوده ایجاد و مدیریت کنید.fun main() {println(&quot;شروع برنامه&quot;)// ایجاد یک CoroutineScope محدودval scope = CoroutineScope(Dispatchers.Default)// ایجاد کوروتین در داخل CoroutineScopescope.launch {delay(1000)println(&quot;کوروتین&quot;)}println(&quot;پایان برنامه&quot;)}ViewModelScope:در واقع ViewModelScope یک نوع خاص از CoroutineScope است که برای استفاده در کلاسهای ViewModel در پروژه‌های Android طراحی شده است. با استفاده از ViewModelScopeمی‌توانید کوروتین‌ها را در محدوده ViewModel ایجاد و مدیریت کنید. ViewModelScope خود به طور پیش‌فرض از Dispatchers.Mainاستفاده می‌کند.مثال:class MyViewModel : ViewModel() {fun fetchData() {viewModelScope.launch {delay(1000)println(&quot;اطلاعات دریافت شد&quot;)}}}در این مثال، ما یک ViewModel به نام MyViewModel داریم که دارای یک تابع fetchData است. در داخل این تابع، ما از viewModelScopeاستفاده می‌کنیم تا یک کوروتین را در محدوده ViewModelایجاد کنیم و پس از تاخیر ۱ ثانیه، یک پیام چاپ کنیم.lifecyclescop در واقع LifecycleScope یک نوع CoroutineScope خاص است که برای استفاده در پروژه‌های Androidو هماهنگی با دوره حیات (Lifecycle) مورد استفاده قرار می‌گیرد. این نوع CoroutineScopeبا استفاده از Lifecycle مربوطه، مدیریت مناسبی برای کوروتین‌ها در طول عمر مرتبط با Lifecycleفراهم می‌کند.زمانی که یک کلاس از LifecycleOwner را پیاده‌سازی می‌کند (مانند Activity یا Fragment)، می‌توان از LifecycleScope استفاده کرد. LifecycleScopeبه طور پیش‌فرض از Dispatchers.Main استفاده می‌کند.یک مثال ساده از استفاده از LifecycleScope در یک Activity:class MainActivity : AppCompatActivity() {override fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)setContentView(R.layout.activity_main)// استفاده از LifecycleScopeدر ActivitylifecycleScope.launch {delay(1000)println(&quot;کوروتین&quot;)}}}در این مثال، ما یک Activity به نام MainActivity داریم که از AppCompatActivity ارث‌بری می‌کند. در داخل تابع onCreate، ما از lifecycleScope استفاده می‌کنیم تا یک کوروتین در محدوده Lifecycle ایجاد کنیم. در اینجا، پس از تاخیر ۱ ثانیه، یک پیام چاپ می‌شود.با استفاده از LifecycleScope، کوروتین‌ها متناسب با دوره حیات مربوطه مدیریت می‌شوند. به عنوان مثال، اگر Activityمتوقف شود یا از حالت مخفی به حالت قابل مشاهده برگردد، کوروتین‌های مرتبط با آن به توقف و از سرگیری خودکار می‌شوند.استفاده از LifecycleScope به شما امکان می‌دهد که کوروتین‌ها را با توجه به دوره حیات مربوطه مدیریت کنید و از مشکلات مربوط به شروع و اتمام مناسب کوروتین‌ها در زمان‌های نامناسب جلوگیری کنید.به طور کلی، CoroutineScope و lifecycleScope هر دو روش هایی هستند که به شما امکان می‌دهند کوروتین‌ها را در برنامه خود ایجاد کنید و مدیریت کنید. اما تفاوت اصلی بین آنها در دامنه زمانی فعالیت کوروتین‌ها است. نوع CoroutineScope به شما اجازه می‌دهد دامنه زمانی دلخواه خود را تعیین کنید، در حالی که lifecycleScope به صورت خودکار با دوره عمر (lifecycle) مربوط به کلاسی که در آن استفاده می‌کنید، هماهنگ می‌شود.در مثال زیر، تفاوت بین CoroutineScope و lifecycleScope را بررسی می‌کنیم:class MyActivity : AppCompatActivity(), CoroutineScope by MainScope() {    override fun onCreate(savedInstanceState: Bundle?) {        super.onCreate(savedInstanceState)        setContentView(R.layout.activity_my)        // استفاده از CoroutineScope        val myScope = CoroutineScope(Dispatchers.Main)        myScope.launch {            // کوروتین‌ها در دامنه زمانی CoroutineScope اجرا می‌شوند            doTaskOne()            doTaskTwo()        }        // استفاده از lifecycleScope        lifecycleScope.launch {            // کوروتین‌ها در دامنه زمانی lifecycleScope اجرا می‌شوند            doTaskOne()            doTaskTwo()        }    }    override fun onDestroy() {        super.onDestroy()        // لغو تمام کوروتین‌های موجود در دامنه زمانی CoroutineScope        myScope.cancel()    }    private suspend fun doTaskOne() {        // عملیات طولانی مدت    }    private suspend fun doTaskTwo() {        // عملیات طولانی مدت    }}در این مثال، MyActivity یک فعالیت (Activity) در اندروید است که از CoroutineScope استفاده می‌کند. در onCreate، دو کوروتین با استفاده از CoroutineScope ایجاد می‌شوند و در دامنه زمانی CoroutineScope اجرا می‌شوند.از طرف دیگر، lifecycleScope استفاده شده در بلوک lifecycleScope.launch، با دوره عمر فعالیت (MyActivity) هماهنگ می‌شود. این به این معنی است که کوروتین‌های درون lifecycleScope به طور خودکار لغو می‌شوند و منابع آنها را آزاد می‌کنند هنگامی که فعالیت منتهی می‌شود.در onDestroy، ما کوروتین‌های موجود در دامنه زمانی CoroutineScope را با فراخوانی myScope.cancel() لغو می‌کنیم، در حالی که کوروتین‌های موجود در lifecycleScope به طور خودکار لغو می‌شوند.بنابراین، تفاوت اصلی بین CoroutineScope و lifecycleScope در مدیریت دامنه زمانی کوروتین‌ها است. CoroutineScope به شما اجازه می‌دهد دامنه زمانی دلخواهخود را تعیین کنید و باید به صورت دستی کوروتین‌ها را لغو کنید. از طرف دیگر، lifecycleScope به صورت خودکار با دوره عمر فعالیت هماهنگ می‌شود و کوروتین‌ها به طور خودکار لغو می‌شوند هنگامی که فعالیت به پایان می‌رسد.حالا بریم ببینیم Dispatchers  ها چیا هستند!!🤔🤔در کتابخانه کوروتین Dispatchers مسئول مدیریت و ارسال کوروتین‌ها به نخ‌های مختلف است. Dispatchers به شما امکان می‌دهد که کوروتین‌ها را در نخ‌های مختلف اجرا کنید، از جمله نخ اصلی (Main)، نخ‌های پس‌زمینه(Background) و نخ‌های IO.زیر مجموعه‌های مختلفی از Dispatchers درKotlin وجود دارد، که هر کدام برای مورد استفاده خاصی طراحی شده‌اند. در زیر، به برخی از Dispatchers اصلی اشاره می‌کنم و هر کدام را با مثال توضیح می‌دهم:Dispatchers.Main: از  Dispatchers.Main  برای اجرای کوروتین‌ها در نخ اصلی (Main Thread) استفاده می‌شود. این نخ اصلی برای  انجام عملیات‌های رابط کاربری (UI) در پروژه‌های Android بسیار مهم است. استفاده از     Dispatchers.Main در کوروتین‌ها از بروز خطاهای مربوط به تغییرات رابط کاربری در نخ نامناسب جلوگیری می‌کند.مثال:fun main() {println(&quot;شروع برنامه&quot;)// اجرای کوروتین در نخ اصلیrunBlocking(Dispatchers.Main) {delay(1000)println(&quot;کوروتین&quot;)}println(&quot;پایان برنامه&quot;)}Dispatchers.Default:  از  Dispatchers.Default برای اجرای کوروتین‌ها در نخ‌های   پس‌زمینه (Background Thread) استفاده می‌شود.  این نوع از Dispatchers برای عملیات‌های   محاسباتی متوسط ​​تا سنگین مناسب است. معمولاً برای اجرای عملیات همگام سنگین مانند محاسبات پیچیده استفاده می‌شود.مثال:fun main() {println(&quot;شروع برنامه&quot;)// اجرای کوروتین در نخ پس‌زمینهrunBlocking(Dispatchers.Default) {delay(1000)println(&quot;کوروتین&quot;)}println(&quot;پایان برنامه&quot;)}Dispatchers.IO:    از Dispatchers.IO برای اجرای کوروتین‌ها در نخ‌های   ورودی/خروجی (IO Thread) استفاده می‌شود. این      نوع از Dispatchers برای عملیات  ورودی/خروجی شبکه، فایل، دیتابیس و سایر عملیات ورودی/خروجی طولانی مدت مناسب   است. در اینجا، کوروتین‌ها در نخ‌های پس‌زمینه اجرا می‌شوند تا از بلاک شدن   نخ اصلی جلوگیری کنند.مثال:fun main() {println(&quot;شروع برنامه&quot;)// اجرای کوروتین در نخ ورودی/خروجیrunBlocking(Dispatchers.IO) {delay(1000)println(&quot;کوروتین&quot;)}println(&quot;پایان برنامه&quot;)}این مثال‌ها نشان می‌دهند که با استفاده از Dispatchers مختلف ، می‌توانید کوروتین‌ها را در نخ‌های مختلف اجرا کنید و بهترین عملکرد و عدم بلاک شدن نخ اصلی را داشته باشید. با انتخاب صحیحDispatchers مناسب برای نوع عملیاتی که قصد انجام آن را دارید، می‌توانید کارایی و پاسخگویی برنامه خود را بهبود بخشید.به عنوان نکته باید بگم که کوروتین‌ها در زبان Kotlin از نوعStackless هستند، به این معنی که هر کوروتین به طور خودکار از یک استک جداگانه استفاده نمی‌کند. در عوض، از مکانیزمی به نام&quot;Continuation Passing Style&quot; (CPS) برای مدیریت و انتقال ادامه کار بین کوروتین‌ها استفاده می‌کند.درCPS، هر کوروتین یک شیء به نام&quot;Continuation&quot; را به عنوان ورودی دریافت می‌کند. این Continuation شامل ادامه کاری است که باید پس از اتمام کوروتین اجرا شود. به این ترتیب، هنگامی که یک کوروتین متوقف می‌شود، Continuation مربوطه به کوروتین بعدی منتقل می‌شود و اجرای برنامه از همان نقطه ادامه می‌یابد.اگر کوروتین‌هاStackful بودند، در هنگام اجرای تابعmyCoroutine()، یک استک جداگانه برای آن ایجاد می‌شد و توابع برنامه در تابع myCoroutine() به صورت پشته‌ای در هم قرار می‌گرفتند. اما با استفاده از CPS، هر کوروتین تنها یک Continuation را دریافت می‌کند و اجرای برنامه بین کوروتین‌ها منتقل می‌شود.استفاده از کوروتین‌های Stackless مزایایی مانند کارایی بالا، مدیریت مناسب حافظه و عدم احتمال بروز Deadlock و Stack Overflow را به همراه دارد.آشنایی با Deferred,Job و متدهای  Join , JoinAll , await,cancel , isCancelled isCompleted, getCompletedخب حالا بریم سراغ توابعی که هنگام کار با کوروتین ها لازممون میشه.در حقیقت، launch و async توابعی هستند که کوروتین‌ها را شروع می‌کنند و خودشان مستقیماً توابع خاصی ندارند که فقط روی آن‌ها صدا زده شوند. اما، آن‌ها چیزهایی را برمی‌گردانند (مثلاً Job و Deferred&lt;T&gt; به ترتیب) که می‌توان با آن‌ها کارهایی انجام داد.برای launch:تابع launch یک Job برمی‌گرداند. روی این Job می‌توان توابع زیر را صدا زد:تابع  cancel(): برای کنسل کردن کوروتین.تابع  join(): منتظر می‌ماند تا کوروتین مربوطه کامل شود.تابع  isCancelled: برای بررسی که آیا کوروتین کنسل شده است یا خیر.تابع  isActive: بررسی می‌کند که آیا کوروتین هنوز فعال است.برای async:از   async یک Deferred&lt;T&gt; برمی‌گرداند که یک نوع خاص از Job است و علاوه بر توابع موجود در Job، توابع زیر را نیز دارد:تابع  await(): منتظر می‌ماند تا نتیجه (یا خطا) از عملیات آسنکرون برگردد.تابع  isCompleted: بررسی می‌کند که آیا عملیات آسنکرون کامل شده است.تابع  getCompleted(): اگر عملیات کامل شده باشد، نتیجه را بلافاصله برمی‌گرداند.نکته مهم:این توابع و خاصیت‌ها مستقیماً به launch و async تعلق ندارند، بلکه به اشیاء برگشت داده شده از این توابع (یعنی Job و Deferred&lt;T&gt;) تعلق دارند. این نکته مهمی است زیرا launch و async خودشان تنها کوروتین‌ها را شروع می‌کنند و مدیریت زندگی کوروتین‌ها از طریق اشیاء برگردانده شده از آن‌ها انجام می‌گیرد.خب حالا بریم یک مثال کلی برای درک بهتر این توابع بزنیم 😍😍fun main() {    val job1: Job = GlobalScope.launch {        delay(1000)        println(&quot;Job 1 is complete&quot;)    }    val deferred1: Deferred&lt;String&gt; = GlobalScope.async {        delay(1500)        return@async &quot;Deferred 1 result&quot;    }    val deferred2: Deferred&lt;String&gt; = GlobalScope.async {        delay(2000)        return@async &quot;Deferred 2 result&quot;    }    runBlocking {        job1.join()        println(&quot;Job 1 has joined&quot;)        val result1 = deferred1.await()        println(&quot;Deferred 1 result: $result1&quot;)        val result2 = deferred2.await()        println(&quot;Deferred 2 result: $result2&quot;)        val job2: Job = launch {            delay(500)            println(&quot;Job 2 is complete&quot;)        }        val job3: Job = launch {            delay(700)            println(&quot;Job 3 is complete&quot;)        }        val job4: Job = launch {            delay(900)            println(&quot;Job 4 is complete&quot;)        }        joinAll(job2, job3, job4)        println(&quot;All jobs have completed&quot;)        job2.cancel()        if (job2.isCancelled) {            println(&quot;Job 2 is cancelled&quot;)        }        if (job2.isCompleted) {            println(&quot;Job 2 is completed&quot;)        }        val completedJobs: List&lt;Job&gt; = listOf(job1, job2, job3, job4).filter { it.isCompleted }        println(&quot;Completed jobs: $completedJobs&quot;)        val completedResults: List&lt;String&gt; = listOf(deferred1, deferred2).filter { it.isCompleted }.map { it.getCompleted() }        println(&quot;Completed results: $completedResults&quot;)    }}در این مثال، ابتدا یک Job با استفاده از launch ایجاد می‌شود که دارای تاخیر 1 ثانیه است. سپس دو Deferred با استفاده از async ایجاد می‌شوند که هر کدام دارای تاخیر‌های مختلفی هستند. سپس با استفاده از join و await منتظر اتمام هر Job و Deferred می‌مانیم و نتایج را دریافت می‌کنیم.سپس سه Job دیگر با استفاده از launch ایجاد می‌شوند و با استفاده از joinAll منتظر اتمام همه آن‌ها می‌مانیم. سپس با استفاده از cancel و isCancelled بررسی می‌کنیم که آیا یک Job لغو شده است یا خیر. همچنین با استفاده از isCompleted بررسی می‌کنیم که آیا یک Job تکمیل شده است یا خیر.در آخر، با استفاده از filter و map، Jobها و Deferredهایی که تکمیل شده‌اند را انتخاب و نتایج آن‌ها را دریافت می‌کنیم. خروجی این مثال به صورت زیر خواهد بود:Job 1 is completeJob 1 has joinedDeferred 1 result: Deferred 1 resultDeferred 2 result: Deferred 2 resultJob 2 is completeJob 3 is completeJob 4 is completeAll jobs have completedJob 2 is cancelledCompleted jobs: [Job(active), Job#e768, Job#4d6c]Completed results: [Deferred 1 result, Deferred 2 result]در قسمت &#x60;Completed jobs&#x60;، شناسه‌های &#x60;Job&#x60;هایی که تکمیل شده‌اند نشان داده شده‌اند. همچنین در قسمت &#x60;Completed results&#x60;، نتایج &#x60;Deferred&#x60;هایی که تکمیل شده‌اند نشان داده شده‌اند.امیدوارم براتون مفید واقع شده باشه.پیشنهاد میکنم پست بعدی هم مطالعه کنیدمدیریت خطاها در کوروتین ها</description>
                <category>narjes Mansoori</category>
                <author>narjes Mansoori</author>
                <pubDate>Sat, 24 Feb 2024 15:02:20 +0330</pubDate>
            </item>
                    <item>
                <title>شیرجه ای عمیق تر در مفاهیم Coroutine ها</title>
                <link>https://virgool.io/@narjes.mansoori/%D8%B4%DB%8C%D8%B1%D8%AC%D9%87-%D8%A7%DB%8C-%D8%B9%D9%85%DB%8C%D9%82-%D8%AA%D8%B1-%D8%AF%D8%B1-%D9%85%D9%81%D8%A7%D9%87%DB%8C%D9%85-coroutine-%D9%87%D8%A7-szgzxxzcqxhz</link>
                <description>خب در پست قبلی ما به طور کلی در مورد هدف کوروتین ها و مفاهیم آن آشنا شدیم.در این پست میخواهیم نگاهی عمیق تر به کوروتین ها و عملکرد آن ها بندازیم .به طور کلی Coroutines از ترکیب  Co + Routines تشکیل شده است که مخفف کلمات cooperation  و Routines  میباشد.به معنای همکاری توابع میباشد.خب کوروتین ها به همین صورت کار میکنند.به این معنی که وقتی توابع با یکدیگر همکاری می کنند، آن را Coroutine می نامیم.نمای کلی از همکاری توابع در کوروتین هاحالا قطعه کد زیر را در نظر بگیرید:fun functionA(case: Int) {when (case) {1 -&gt; {taskA1()functionB(1)}2 -&gt; {taskA2()functionB(2)}3 -&gt; {taskA3()functionB(3)}4 -&gt; {taskA4()functionB(4)}}}و حالا کد زیر را ببینید:fun functionB(case: Int) {when (case) {1 -&gt; {taskB1()functionA(2)}2 -&gt; {taskB2()functionA(3)}3 -&gt; {taskB3()functionA(4)}4 -&gt; {taskB4()}}}حالا اگه فراخوانی تایع A را به صورت زیر داشته باشیم :functionA(1)وقتی یک تابع Bرا از تابع A فراخوانی می کنید، Aباید منتظر بماند تا B به پایان برسد تا اجرا ادامه یابد، این همان چیزی است که ما توابع غیر همکار می نامیم. و این کنترل تابع Bبر روی A توسط سیستم عامل انجام می شود، B باید به پایان برسد، بنابراین سیستم عامل می تواند کنترل را به Aبرگرداند. برای اینکه A در این وضعیت نیاز به انتظار B نداشته باشد، باید Bرا در یک ترد دیگر اجرا کنید و اکنون Aنیازی به صبر ندارد.با Coroutines، همکاری فوق را می توان به راحتی انجام داد که بدون استفاده از when (case)  است که در مثال بالا برای درک بهتر از آن استفاده کردم. اکنون متوجه شدیم که وقتی صحبت از همکاری بین توابع به میان می‌آید، منظورمون چیه.پس در نتیجه کوروتین ها توابعی هستند که با هم همکاری دارند، توابعی که در آن کنترل نحوه اجراها توسط «کاربر» و نه سیستم عامل انجام می شود، بنابراین می توانید همین کار را با استفاده از تردهای مختلف یا حتی  یک ترد داشته باشید. و برای داشتن این اثر، کنترل از یک تابع به تابع دیگر منتقل می شود به گونه ای که نقطه خروج از تابع اول و نقطه ورود به تابع دوم به خاطر سپرده می شود. به این ترتیب، می‌توانید این احساس را داشته باشید که وقتی واقعاً از یک ترد استفاده می‌کنید، به ظاهر از چند ترد استفاده می‌کنید و به همین دلیل اغلب می‌شنوید که کوروتین‌ها تردهای سبک هستند.خب این توابعی که با کوروتین ها با هم همکاری میکنند از نوع suspend هستند.مادیفایر Suspend  به کامپایلر اعلام میکند که این تابع میتواند به حالت تعلیق در بیاد(suspend  شود) و مجدد از همان نقطه ای که به حالت تعلیق رفته بود از سر گرفته شود(resume شود).این نقطه را کامپایلر به خاطر خواهد سپرد.پس با مفهوم suspend Function  ها هم آشنا شدیم.حالا قطعه کدهای زیر را در نظر بگیرید که بیشتر مطالب بالا را درک کینم.suspend fun makeLogin(login: String, password: String, callback: (Token) -&gt; Unit) {
// request login
callback(token)
}
suspend fun loadMovies(callback: (List&lt;Movie&gt;) -&gt; Unit) {
makeLogin(&amp;quotsomeValue&amp;quot, &amp;quotsomeValue&amp;quot) { token -&gt;
// request movies with token
callback(movies)
}
}
fun someFunction() {
loadMovies { movies -&gt;
printMovies(movies)
}
}خب در این کد کامپایلر از تابع someFunction خطا میگیرد.چرا؟چون که توابع suspend  فقط باید از توابع suspend  دیگر صدا زده شوند یا از درون کوروتین ها.پس کد را به صورت زیر اصلاح میکنیم که خطا برطرف شودsuspend fun makeLogin(login: String, password: String) : Token {// request loginreturn token}suspend fun loadMovies(token: Token) : List&lt;Movie&gt; {val token = makeLogin(&quot;someValue&quot;, &quot;someValue&quot;)// request movies with tokenreturn movies}fun someFunction() {GlobalScope.launch {val token = makeLogin(&quot;someValue&quot;, &quot;someValue&quot;)val movies = loadMovies(token)printMovies(movies)}}خب حالا دیگه کامپایلر از کد ما خطا نمیگیره چون ما توابع تعلیق خودمون رو داخل یک کوروتین کال کردیم.حالا میبینید که توابع ما چطوری به کمک کوروتین ها با هم همکاری دارند و دیگه حتی نیازی هم به کال بک ها نداریم و اونا رو حذف کردیم.خب حالا اون GlobalScope , launch چیه؟تا اینجا بدون که launch  یک کوروتین در GlobalScope ایجاد میکنه.در اینجا launch  یک لامبدا یا یک تابع بی‌نام (anonymous function) از نوع suspend  به عنوان ورودی میگرید.(همان کدی که درون بلاک launch  است.  (اجازه بده در پست بعدی کامل برات توضیح بدم)خب بریم کد رو توضیح بدیم چطوری کار میکنه!! makeLogin و loadMovies توابع asyncهستند، ممکن است چند ثانیه طول بکشد تا تمام شوند، اما شبیه توابع غیر همگام هستند. هنگامی که makeLogin فراخوانی می شود، تابع launch به حالت تعلیق در می آید و زمانی که makeLogin تمام شد از سر گرفته می شود و با فراخوانی loadMovies همین اتفاق می افتد. اگر داخل loadMovies یک تابع suspendدیگری بود، تا زمانی که تابع دیگر تمام شود به حالت تعلیق در می آمد.توابع makeLogin و loadMovies ممکن است در تردهای مختلف انجام شوند زمانی که در یک محیط کوروتینی، به صورت همروند (concurrently) فراخوانی شوند یا اگر از کوروتین‌های متفاوتی برای فراخوانی آنها استفاده شود. به طور خاص:1. اگر توابع makeLogin و loadMovies به صورت همروند (concurrently) فراخوانی شوند، به این معناست که اجرای آنها ممکن است همزمان در حال اجرا در تردهای مختلف باشد. برای مثال، اگر در یک محیط کوروتینی، دو کوروتین مستقل ایجاد شود که هر کدام از این توابع را فراخوانی کنند، ممکن است هر کدام از این توابع در تردهای مختلفی اجرا شوند.2. اگر از کوروتین‌های متفاوتی برای فراخوانی توابع استفاده شود، هر تابع ممکن است در تردی مجزا اجرا شود. برای مثال، اگر تابع makeLogin در یک کوروتین و تابع loadMovies در کوروتین دیگری فراخوانی شود، ممکن است هر کدام از این توابع در تردهای مختلفی اجرا شوند.بنابراین، زمانی که توابعsuspend در کوروتین‌ها فراخوانی می‌شوند و برنامه اصلی همچنین در یک کوروتین اجرا می‌شود، می‌توان انتظار داشت که این توابع در تردهای مختلفی اجرا شوند، به ویژه اگر به صورت همروند فراخوانی شوند.خب حالا به شکل زیر نگاه کن تا مطالب بالا رو بیشتر بتونی درک کنیاجرای توابع suspend در یک ترد پیشنهاد میکنم پست بعدی رو حتما در ادامه این پست مطالعه کنیدامیدوارم مفید براتون واقع شده باشد.آشنایی با مفاهیم Coroutine dispatcher,Coroutine builder, Coroutine Scope</description>
                <category>narjes Mansoori</category>
                <author>narjes Mansoori</author>
                <pubDate>Sat, 24 Feb 2024 11:05:40 +0330</pubDate>
            </item>
                    <item>
                <title>(Coroutines)کوروتین ها در اندروید</title>
                <link>https://virgool.io/@narjes.mansoori/coroutines%DA%A9%D9%88%D8%B1%D9%88%D8%AA%DB%8C%D9%86-%D9%87%D8%A7-%D8%AF%D8%B1-%D8%A7%D9%86%D8%AF%D8%B1%D9%88%DB%8C%D8%AF-fukpsevzwmu6</link>
                <description>سلام بچه ها امیدوارم که حالتون خوب باشه.خب توی این مقاله میخوایم که هر آن چیزی که به عنوان یک AndroidDeveloper در مورد کوروتین ها باید بدانید را با هم بررسی کنیم.خب اول از همه اینکه آقا این کوروتین اصلا چی هست؟🤔قبل از شروع اینو بگم که پیشنیاز این مقاله آشنایی کافی با مفاهیم تردینگ میباشد.خب همه ما در حین توسعه اپلیکیشنمون یک سری تسک هایی داریم که اصطلاحا بهشون میگن LongTask. خب یعنی چی؟یعنی اینکه این تسک ها برای اجرا و کامل شدن زمان زیادی لازم دارند که اگه اونا رو روی ترد اصلی یا یوآی ترد اجرا کنیم اپلیکیشن ما اصطلاحا فریز میشه یا lock میشه و خب این تجربه کاربری بدی برای اپلیکیشن ما خواهد بود.خب راه حل چیه؟راه حل های قدیمی و سنتی بدین صورت بود که شما در اندروید میتوانستید با ایجاد یک بکگراند ترد این عملیات را از ترد اصلی جدا کنید.خب این راه حل خیلی جالب به نظر نمیرسه چرا که مدیریت دستی ترد ها کاری زمانبر و پیچیده خواهد بود و علاوه بر اینکه خوانایی کد پایین میاد (مشکل callback hell) و هم اینکه ممکن هست که با مشکل MemoryLeak  برخورد کنیم چرا که هر ترد حافظه ی جداگانه خود را دارد و مدیریت دستی این موارد واقعا کاری پیچیده خواهد بود.خب بریم توضیحات بالا رو با مثال توضیح بدیم:فرض کنید ما یک ApiCall  برای گرفتن اطلاعات یوزر از سرور داریم .حالا اگه کد زیر را برای این منظور به صورت زیر بنویسیم:fun fetchAndShowUser() {val user = fetchUser()showUser(user)}fun fetchUser(): User {// make network call// return user}fun showUser(user: User) {// show user}خب اگه این کد را اجرا کنیم ، ما یک exception  از نوع NetworkOnMainThreadException دریافت خواهیم کرد و این یعنی اینکه ما اجازه استفاده از این درخواست را روی MainThread  نداریم .خب راه حل چیه؟استفاده از Callback .حالا کد زیر را در نظر بگیرید:fun fetchAndShowUser() {fetchUser { user -&gt;showUser(user)}}fun fetchUser(callback: (User) -&gt; Unit)) {// make network call on background thread to get user// callback with usercallback(user)}fun showUser(user: User) {// show user}خب اوکی مشکل ما حل شد و دیگه ما NetworkOnMainThreadException دریافت نخواهیم کرد.اما اگه ما چندتا درخواست تودرتو داشته باشیم چی میشه؟حالا کد زیر را در نظر بگیر:fun fetchData() {fetchA { a -&gt;fetchB(a) { b -&gt;fetchC(b) { c -&gt;// do something with c}}}}خب به این نوع پیاده‌سازی، که توابع به صورت متوالی یکدیگر را فراخوانی می‌کنند و از نتیجه هرکدام برای فراخوانی تابع بعدی استفاده می‌کنند، به عنوان &quot;Callback Hell&quot; شناخته می‌شود. در این روش، به دلیل استفاده از بازخوانی های متعدد و تودرتویی، کد ممکن است به شکل ناخوانا و سخت‌ به نظر بیاید. و حالا فکر کنید اگر این درخواست ها بیشتر و بیشتر بشه چی میشه و چه  و دردسرهایی خواهیم داشت.تا اینجا افتاد دیگه؟خب حالا بریم دنبال راه حل های بهتر.خب برای خلاص شدن از این مشکلات میتونیم بیایم از ابزار قدرتمند RX استفاده کنیم.به صورت زیر :fetchUser().subscribeOn(Schedulers.io()).observerOn(AndroidSchedulers.mainThread()).subscribe { user -&gt;showUser(user)}fun fetchUser(): Single&lt;User&gt; {// make network call// emit user}fun showUser(user: User) {// show user}خب اوکی الان با RX تونستیم مشکل callback hell رو هم حل کنیم.(ما توی این مقاله در همین حد از RX توضیح میدیم و توضیحات بیشتر در این مبحث نمیگنجه).خب حالا با وجود اینکه RX راه حلی عالی بود و همچنان هم هست اما خب بعضی مقالات به منحنی یادگیری RX اشاره کردن.یعنی اینکه یادگیری RX زمانبر تر هست و هم اینکه خب حجم کدنویسی Rx برای هندل کردن کدهای Async  بیشتر خواهد بود.خب تا همینجا کافیه .حالا بریم برای بحث شیرین coroutine ها .😍کوروتین یک ابزار یا فریم ورک یا یک الگوی برنامه‌نویسی است که امکان اجرای همروند و ناهمگام(asynchronous )  را فراهم می‌کند. این الگو به برنامه‌نویسان امکان می‌دهد تا به صورت ساده‌تر با عملیات‌های همروند کار کنند و از منابع سیستم به بهترین شکل استفاده کنند.کوروتین ها را اصطلاحا تردهای سبک وزن میگن یعنی اینکه شما میتونید چندین کرورتین را در یک ترد داشته باشید.(نگران نباش بهت قول میدم در پست بعد کامل برات توضیح بدم یعنی چی)نکته مهم دیگه اینه که استفاده از کوروتین ها خیلی ساده ست یعنی اینکه شما همانطور که کد های sequential (کدهای sequential به صورت متوالی و پشت سر هم اجرا می‌شوند، به این معنی که هر دستور باید منتظر اجرای دستور قبلی باشد تا بتواند اجرا شود. )کد های خود را مینویسید به همان صورت هم میتوانید کدهای Async  خود را به کمک کوروتین ها بنویسید که از نمای بصری ساده تر و خوانایی کد بالاتر خواهد بود و این از زیبایی های کوروتین هاست😊.به عنوان مثال: نمونه کد زیر را در نظر بگیریدfun fetchAndShowUser() {GlobalScope.launch(Dispatchers.Main) {val user = fetchUser() // fetch on IO threadshowUser(user) // back on UI thread}}suspend fun fetchUser(): User {return withContext(Dispatchers.IO) {// make network call on IO thread// return user}}fun showUser(user: User) {// show user}تیکه کد بالا یک نمونه ساده از استفاده و پیاده سازی کوروتین هاست.میبینید به چه سادگی و زیایی تونستیم درخواست شبکه رو روی یک IOThread اجرا کنیم و نتیجه رو نشون بدیم.😍😍خب اصلا نگران نباشید در مورد کدهای بالا و کلمات کلیدی آن در مقاله بعدی توضیح خواهیم داد. تا اینجا که متوجه شدیم هدف از استقاده کوروتین ها چیه.شیرجه ای عمیق تر درون کوروتین ها&lt;br/&gt;</description>
                <category>narjes Mansoori</category>
                <author>narjes Mansoori</author>
                <pubDate>Sat, 17 Feb 2024 14:27:48 +0330</pubDate>
            </item>
                    <item>
                <title>اصول SOLID به زبان ساده - اصل پنجم</title>
                <link>https://virgool.io/@narjes.mansoori/%D8%A7%D8%B5%D9%88%D9%84-solid-%D8%A8%D9%87-%D8%B2%D8%A8%D8%A7%D9%86-%D8%B3%D8%A7%D8%AF%D9%87-%D8%A7%D8%B5%D9%84-%D9%BE%D9%86%D8%AC%D9%85-srrggsgcbgjo</link>
                <description>اصول SOLID به زبان ساده - اصل اولاصول SOLID به زبان ساده - اصل دوماصول SOLID به زبان ساده - اصل سوماصول SOLID به زبان ساده - اصل چهارماصل پنجم و آخر SOLID، اصل وارونگی وابستگی (Dependency Inversion Principle) نام داره که به اختصار DIP گفته میشه. توضیح رسمی و آکادمیک این اصل به صورت زیر هست. این توضیح رو بخونید تا با هم ریز به ریز جزییاتش رو بررسی کنیم:کلاس‌های سطح بالا نباید به کلاس‌های سطح پایین وابسته باشن؛ هر دو باید وابسته به انتزاع (Abstractions) باشن. موارد انتزاعی نباید وابسته به جزییات باشن. جزییات باید وابسته به انتزاع باشنخب دوستان این توضیحی بود که خیلی آکادمیک و یکم گنگ هست. مواردی مثل کلاس سطح بالا و سطح پایین، انتزاع و جزییات مواردی هستن که باید روشن بشن تا بتونیم این اصل رو خوب درک کنیم.کلاس سطح پایین چیه؟? به کلاس‌هایی گفته میشه که مسئول عملیات اساسی و پایه‌ای توی نرم‌افزار هستن. مثل کلاسی که با دیتابیس یا هارددیسک ارتباط برقرار می‌کنه، کلاسی که برای ارسال ایمیل استفاده میشه و ...کلاس سطح بالا؟ ?کلاس‌هایی که عملیات پیچیده‌تر و خاص‌تری انجام میدن و برای انجام این کار از کلاس‌های سطح پایین استفاده میکنن. برای مثال کلاس گزارش‌گیری برای ثبت و خوندن گزارش، به کلاس دیتابیس یا هارددیسک نیاز داره. کلاس Users، برای اطلاع‌رسانی به کاربرها به کلاس ایمیل نیاز داره.مفهوم انتزاع (Abstraction)کلاس‌های انتزاعی کلاس‌های هستن که قابل پیاده‌سازی نیستن اما به عنوان یک طرح و الگو برای کلاس‌های دیگه در نظر گرفته میشن. مثلا یک کلاس انتزاعی برای گربه، زرافه، پلنگ و پنگوئن، میشه کلاس Animal. خود Animal به خودی خود قابل پیاده‌سازی نیست. بلکه یک طرح کلی برای حیوونایی هستن که مثال زدیم. پس تک تک این حیوون‌ها یک ورژن کلی‌تر دارن که می‌تونیم اون رو Animal بنامیم. پیشنهاد می‌کنم این مقاله‌ی مفصل درباره مفهوم انتزاع رو بخونید:درک مفهوم انتزاع (Abstraction) در شی‌گرایی به زبان سادهمفهوم انتزاع یا Abstraction توی برنامه‌نویسی شی‌گرا چیزی هست که باید درک بشه و ما توی این مقاله بطور مفصل با اون آشنا میشیممفهوم جزییاتمنظور از جزییات توی تعریف این اصل، جزییات یک کلاس مثل نام و ویژگی پراپرتی‌ها و متدهاست.خب بپردازیم به بررسی این اصل. ابتدا کد زیر رو در نظر بگیرید:class MySql {
    public insert() {}
    public update() {}
    public delete() {}
}

class Log {
    private database;

    constructor() {
        this.database = new MySql;
    }
}فرض کنیم یک کلاس سطح پایین داریم مثلا دیتابیس MySql. و یک سری کلاس سطح بالا مثلاً گزارش‌گیری (Log) از این کلاس استفاده می‌کنه. اگه بخوایم یک تغییر توی کلاس دیتابیس انجام بدیم، ممکنه بطور مستقیم تاثیر بذاره روی کلاس‌هایی که ازش استفاده میکنن. مثلا اگه توی کلاس MySql اسم متد رو تغییر بدیم و یا پارامترها رو کم و زیاد کنیم، نهایتا توی کلاس Log این تغییرات رو باید اعمال کنیم.همچنین کلاس‌های سطح بالا قابل استفاده مجدد نیستن. مثلاً اگه بخوایم برای کلاس Log از دیتابیس‌های دیگه مثلا MongoDB یا هارددیسک استفاده کنیم باید کلاس Log رو تغییر بدیم یا یک کلاس جدا براساس هر نوع دیتابیس بسازیم.خب همونطور که می‌بینید اگه یک کلاس سطح بالا وابسته به یک کلاس سطح پایین باشه این مشکلات به وجود میاد.راه حلبرای حل این مشکل باید با اینترفیس، یک لایه انتزاعی درست کنیم. با این کار کلاس Log دیگه وابسته به یک کلاس خاص برای ذخیره‌سازی و خوندن اطلاعات نیست و می‌تونیم هر نوع دیتابیسی رو استفاده کنیم و برای کلاس Log اهمیتی نداره که با چه نوع دیتابیسی داره کار میکنه. چون وابسته به انتزاع هست.ابتدا یک اینترفیس میسازیم برای اینکه کلاس‌های سطح بالا و سطح پایین رو وابسته به این اینترفیس کنیم:interface Database {
    insert();
    update();
    delete();
}حالا کلاس‌های سطح پایین باید این اینترفیس رو پیاده‌سازی کنن تا وابسته به انتزاع بشن:class MySql implements Database {
    public insert() {}
    public update() {}
    public delete() {}
}

class FileSystem implements Database {
    public insert() {}
    public update() {}
    public delete() {}
}

class MongoDB implements Database {
    public insert() {}
    public update() {}
    public delete() {}
}و نهایتاً توی کلاس‌های سطح بالا، وابستگی به یک کلاس خاص رو به اینترفیس واگذار می‌کنیم. کلاس‌های سطح بالا زمانی وابسته به انتزاع میشن که بجای استفاده مستقیم از کلاس‌های سطح پایین، از یک اینترفیس (رابط) استفاده کنن:class Log {
    private db: Database;

    public setDatabase(db: Database) {
        this.db = db;
    }

    public update() {
        this.db.update();
    }
}همونطور که می‌بینیم وابستگی به یک کلاس خاص از بین رفت و میتونیم هر نوع دیتابیسی رو برای کلاس Log استفاده کنیم:logger = new Log;

logger.setDatabase(new MongoDB);
// ...
logger.setDatabase(new FileSystem);
// ...
logger.setDatabase(new MySql);

logger.update();نتیجه‌گیریمثل بقیه اصول SOLID، این اصل هم تلاش داره وابستگی بین اجزا رو کمتر کنه تا بتونیم کدهای قابل نگهداری، تمیزتر و قابل توسعه‌تر بنویسیم. اما در نظر داشته باشید که مثل بقیه اصول توی دنیای برنامه‌نویسی، این اصل هم باید با چشم باز اعمال بشه. گاهی وقتا اعمال کردن یک سری اصول نه تنها مشکل رو حل نمی‌کنه، بلکه باعث پیچیده‌تر شدن و گنگ شدن کد برنامه میشه.اصول SOLID به زبان ساده - اصل چهارم</description>
                <category>narjes Mansoori</category>
                <author>narjes Mansoori</author>
                <pubDate>Tue, 17 Oct 2023 15:08:54 +0330</pubDate>
            </item>
                    <item>
                <title>اصول SOLID به زبان ساده - اصل چهارم</title>
                <link>https://virgool.io/@narjes.mansoori/%D8%A7%D8%B5%D9%88%D9%84-solid-%D8%A8%D9%87-%D8%B2%D8%A8%D8%A7%D9%86-%D8%B3%D8%A7%D8%AF%D9%87-%D8%A7%D8%B5%D9%84-%DA%86%D9%87%D8%A7%D8%B1%D9%85-epy1jhtmuvoa</link>
                <description>اصول SOLID به زبان ساده - اصل اولاصول SOLID به زبان ساده - اصل دوماصول SOLID به زبان ساده - اصل سوماصل چهارم از SOLID اصل جداسازی اینترفیس‌ها یا Interface Segregation Principle هست که به اختصار ISP گفته میشه. توضیح رسمی و آکادمیک این اصل بصورت زیر هست:کلاس‌ها نباید مجبور باشن متدهایی که به اونها احتیاجی ندارن رو پیاده‌سازی کنناین اصل میگه که ما باید اینترفیس (Interface) ها رو جوری بنویسیم که وقتی یک کلاس از اون استفاده میکنه، مجبور نباشه متدهایی که لازم نداره رو پیاده‌سازی کنه. یعنی متدهای بی‌ربط نباید توی یک اینترفیس کنار هم باشن. این اصل شباهت زیادی به اصل اول SOLID داره که میگه کلاس‌ها باید فقط مسئول انجام یک کار باشن.اینترفیس زیر رو درنظر بگیرید:interface Animal {
    fly();
    run();
    eat();
}این اینترفیس سه متد داره که باید توسط کلاس‌هایی که ازش استفاده میکنن پیاده‌سازی بشه. کلاس Dolphin (دلفین) رو در نظر بگیرید که از این اینترفیس استفاده میکنه:class Dolphin implements Animal {
    public fly() {
        return false;
    }

    public run() {
        // Run
    }

    public eat() {
        // Eat
    }
}همونطور که میدونید، دلفین‌ها نمیتونن پرواز کنن. پس ما مجبور شدیم توی متد fly بنویسیم return false. اینجا قانون ISP نقض شد. چون کلاس دلفین مجبور به پیاده‌سازی متدی شد که از اون استفاده نمیکنه.اگه بخوایم این اصل رو رعایت کنیم باید جداسازی اینترفیس انجام بدیم. پس متد fly رو به یک اینترفیس جدا منتقل میکنیم:interface Animal {
    run();
    eat();
}

interface FlyableAnimal {
    fly();
}بنابراین کلاس دلفین دیگه مجبور نیست متد fly رو پیاده‌سازی کنه و کلاس‌هایی که به این متد نیاز دارن، اینترفیس FlyableAnimal رو هم پیاده‌سازی میکنن:class Dolphin implements Animal {
    public run() {
        // Run
    }

    public eat() {
        // Eat
    }
}

class Bird implements Animal, FlyableAnimal {
    public run() { /* ... */ }
    public eat() { /* ... */ }
    public fly() { /* ... */ }
}نتیجهرعایت کردن این اصل به ما کمک میکنه کدهای خواناتر و تمیزتری داشته باشیم. توی شی‌گرایی باید یک نکته رو درنظر داشته باشیم که هر چی از کلی‌نویسی (عمومی‌نویسی) دوری کنیم و کدهایی داشته باشیم که مجزا و تفکیک شده باشن، برنامه‌ای منسجم‌تر و ساختاریافته‌تر خواهیم داشت. بنابراین کدها قابل استفاده مجدد میشن، تست و Refactor هم راحت‌تر انجام میشه.اصول SOLID به زبان ساده - اصل پنجم</description>
                <category>narjes Mansoori</category>
                <author>narjes Mansoori</author>
                <pubDate>Tue, 17 Oct 2023 15:01:13 +0330</pubDate>
            </item>
            </channel>
</rss>