<?xml version="1.0" encoding="UTF-8"?><rss version="2.0"><channel><title>نوشته های هادی دهقانی</title><link>https://virgool.ir/feed/@hadi.dehghani.t</link><description>توسعه دهنده اندروید با بیش از 10 سال سابقه</description><language>fa</language><pubDate>2026-11-11 11:11:19</pubDate><image><url>https://files.virgool.ir/upload/users/63623/avatar/1KfbO8.png</url><title>هادی دهقانی</title><link>https://virgool.ir/@hadi.dehghani.t</link></image><item><title>درک تفاوت launch و async در Kotlin Coroutines</title><link>https://virgool.ir/@hadi.dehghani.t/درک-تفاوت-launch-و-async-در-kotlin-coroutines-hyrujrut3u9l</link><description>تفاوت launch و asyncمقدمهاگر توسعه‌دهنده اندروید هستید، احتمال زیاد با این صحنه‌ها روبه‌رو شده‌اید: صفحه‌ای که بارگذاریش کُند است، درخواست شبکه‌ای که UI را قفل می‌کند، چند فراخوانی API که انگار قرار نیست تمام بشوند، یا یک کار پس‌زمینه که انیمیشن‌های نرم و روان شما را لگ‌دار می‌کند. آشناست؟ 😅 خیالتان راحت، تنها نیستید. همین‌جاست که Kotlin Coroutines — و به‌خصوص درک تفاوت بین launch و async — وارد میدان می‌شوند و می‌توانند بازی را برای عملکرد اپ شما عوض کنند.ماجرا از کجا شروع شد؟ماه پیش، روی یک اپلیکیشن شبکه اجتماعی کار می‌کردم. توی صفحه پروفایل باید هم اطلاعات کاربر و هم پست‌های اخیرش نمایش داده می‌شد. کد ساده و اولیه این بود:class ProfileViewModel : ViewModel() {&#xA;    fun loadProfile(userId: String) {&#xA;        viewModelScope.launch {&#xA;            val user = userRepository.getUser(userId) // ۱ ثانیه طول می‌کشد&#xA;            val posts = postRepository.getPosts(userId) // ۱ ثانیه طول می‌کشد&#xA;            // مجموعاً: ۲ ثانیه&#xA;            _profileState.value = ProfileState(user, posts)&#xA;        }&#xA;    }&#xA;}نتیجه؟ کاربر باید ۲ ثانیه کامل فقط منتظر می‌ماند تا پروفایلش بالا بیاید. نه فاجعه است، نه خیلی خوب. بدتر این که این دو درخواست API اصلاً به هم ربطی نداشتند و می‌شد آن‌ها را هم‌زمان اجرا کرد.اینجاست که دانستن فرق launch و async می‌تواند معجزه کند.تفاوت اصلی launch و asyncقبل از این که برویم سراغ تکنیک‌ها، بیایید اول خیلی ساده بفهمیم هر کدام چه کار می‌کنند.launch — کارگر «بفرست و ولش کن»launch را تصور کنید مثل این که یک نفر را برای انجام کاری بفرستید ولی انتظار نداشته باشید گزارشی برگرداند. کار خودش را می‌کند و تمام. شما هم رسید کاری که داده‌اید (یک Job) را می‌گیرید و سراغ کار خودتان می‌روید.val job = launch {&#xA;    performBackgroundTask()&#xA;    updateDatabase()&#xA;    // هیچ مقدار بازگشتی وجود ندارد&#xA;}ویژگی‌ها:یک Job برمی‌گرداندهیچ مقداری برنمی‌گرداندبرای کارهایی که فقط اثر جانبی دارند و خروجی مهم نیست، عالی استasync — کارگر «قول نتیجه»async را تصور کنید مثل استخدام یک مشاور که حتماً می‌خواهید بعد از بررسی‌اش گزارش کامل بدهد. وقتی async را صدا می‌زنید، یک وعده نتیجه آینده (Deferred) می‌گیرید، بعد هر وقت خواستید می‌توانید با await() نتیجه را بگیرید.val deferred = async {&#xA;    calculateComplexValue()&#xA;}&#xA;val result = deferred.await()ویژگی‌ها:یک Deferred برمی‌گرداندبا await() می‌توانید مقدار خروجی را بگیریدهم‌زمانی را راحت می‌کند (کارهای موازی)کجا launch و async را استفاده کنیم؟سناریو ۱: سریع‌تر کردن راه‌اندازی اپفرض کنید هنگام باز شدن اپ باید سه کار انجام شود: بارگذاری تنظیمات کاربر، چک کردن آپدیت، و همگام‌سازی داده‌ها.❌ روش کند (ترتیبی): هر کدام بعد از دیگری اجرا می‌شوند.class MainActivity : AppCompatActivity() { &#xA;    override fun onCreate(savedInstanceState: Bundle?) {&#xA;        super.onCreate(savedInstanceState)&#xA;        lifecycleScope.launch { &#xA;            loadUserPreferences() // 500 میلی‌ثانیه &#xA;            checkForUpdates() // 800 میلی‌ثانیه &#xA;            syncUserData() // 1200 میلی‌ثانیه کل: 2.5 ثانیه &#xA;       } &#xA;    } &#xA;} ✅ روش سریع (هم‌زمان): همه با هم اجرا می‌شوند و فقط طولانی‌ترینشان زمان کلی را تعیین می‌کند.class MainActivity : AppCompatActivity() {&#xA;    override fun onCreate(savedInstanceState: Bundle?) {&#xA;        super.onCreate(savedInstanceState)&#xA;        lifecycleScope.launch {&#xA;            val prefsDeferred = async { loadUserPreferences() } &#xA;            val updatesDeferred = async { checkForUpdates() }&#xA;            val syncDeferred = async { syncUserData() } // همه به طور همزمان اجرا می‌شوند، زمان کل: ~1.2 ثانیه &#xA;            val prefs = prefsDeferred.await()&#xA;            val updates = updatesDeferred.await() &#xA;            val syncResult = syncDeferred.await() initializeApp(prefs, updates, syncResult)&#xA;        }&#xA;    }&#xA;}سناریو ۲: ساخت فید خبری سریع‌تراگر همزمان مقالات، اطلاعات کاربر و تبلیغات را دریافت کنیم، به‌جای ۳ ثانیه، کمتر از ۱ ثانیه طول می‌کشد.class NewsFeedViewModel : ViewModel() {&#xA;    fun loadFeed() {&#xA;        viewModelScope.launch {&#xA;            try {&#xA;                // همه عملیات را به طور همزمان شروع کن&#xA;                val articlesDeferred = async {&#xA;                    newsRepository.getLatestArticles()&#xA;                } &#xA;                val userDeferred = async {&#xA;                    userRepository.getCurrentUser()&#xA;                } &#xA;                val adsDeferred = async {&#xA;                    adRepository.getTargetedAds()&#xA;                } // منتظر همه نتایج باش &#xA;                val articles = articlesDeferred.await() &#xA;                val user = userDeferred.await()&#xA;                val ads = adsDeferred.await() // نتایج را ترکیب کن&#xA;                val feedData = FeedData(articles, user, ads)&#xA;                _feedState.value = FeedState.Success(feedData)&#xA;            } catch (e: Exception) {&#xA;                _feedState.value = FeedState.Error(e.message)&#xA;            }&#xA;        }&#xA;    }&#xA;}اشتباهات رایجاستفاده از launch وقتی به نتیجه نیاز داریم → باید async باشد.// ❌ اشتباه - تلاش برای گرفتن نتایج از launch&#xA;fun fetchUserData(): User? {&#xA;    var user: User? = null&#xA;    launch {&#xA;        user = userRepository.getUser() // این طور که انتظار دارید کار نمی‌کند&#xA;    }&#xA;    return user // همیشه null خواهد بود&#xA;}&#xA;&#xA;// ✅ درست - استفاده از async برای نتایج&#xA;suspend fun fetchUserData(): User {&#xA;    return async { userRepository.getUser() }.await()&#xA;}صدا زدن زودهنگام await() که باعث می‌شود هم‌زمانی از بین برود.// ❌ عدم استفاده از همزمانی&#xA;suspend fun loadData() {&#xA;    val user = async { getUserData() }.await()&#xA;    val posts = async { getPostsData() }.await()&#xA;    // اینها به صورت ترتیبی اجرا می‌شوند، هدف را از بین می‌برد&#xA;}&#xA;&#xA;// ✅ اجرای همزمان مناسب&#xA;suspend fun loadData() {&#xA;    val userDeferred = async { getUserData() }&#xA;    val postsDeferred = async { getPostsData() }&#xA;    &#xA;    val user = userDeferred.await()&#xA;    val posts = postsDeferred.await()&#xA;}فراموش کردن مدیریت استثناء‌ها در کارهای هم‌زمان.// ❌ مدیریت ناکامل خطا&#xA;viewModelScope.launch {&#xA;    val result1 = async { riskyOperation1() }&#xA;    val result2 = async { riskyOperation2() }&#xA;    &#xA;    // اگر result1.await() خطا پرتاب کند، result2.await() هرگز فراخوانی نمی‌شود&#xA;    val data1 = result1.await()&#xA;    val data2 = result2.await()&#xA;}&#xA;&#xA;// ✅ مدیریت مناسب استثناء&#xA;viewModelScope.launch {&#xA;    try {&#xA;        val result1 = async { riskyOperation1() }&#xA;        val result2 = async { riskyOperation2() }&#xA;        &#xA;        val data1 = result1.await()&#xA;        val data2 = result2.await()&#xA;        // مدیریت موفقیت&#xA;    } catch (e: Exception) {&#xA;        // مدیریت هر گونه شکست&#xA;    }&#xA;}چه وقت از کدام استفاده کنیم؟از launch استفاده کنید وقتی:فقط کارهای «آتش و فراموش کن» انجام می‌دهید (مثل لاگ کردن، کش کردن)خروجی برایتان مهم نیستاز async استفاده کنید وقتی:به داده خروجی نیاز داریدچند عملیات مستقل دارید که می‌خواهید موازی اجرا شوندنکات پیشرفتهاز coroutineScope برای گروه‌بندی چند کار هم‌زمان استفاده کنیدsuspend fun loadCompleteProfile() = coroutineScope {&#xA;    val basicInfo = async { userRepository.getBasicInfo() }&#xA;    val preferences = async { userRepository.getPreferences() }&#xA;    val statistics = async { userRepository.getStatistics() }&#xA;    &#xA;    ProfileData(&#xA;        basic = basicInfo.await(),&#xA;        prefs = preferences.await(),&#xA;        stats = statistics.await()&#xA;    )&#xA;}برای عملیات طولانی withTimeout بگذاریدsuspend fun loadDataWithTimeout() = withTimeout(5000) {&#xA;    val data1 = async { fetchData1() }&#xA;    val data2 = async { fetchData2() }&#xA;    &#xA;    CombinedData(data1.await(), data2.await())&#xA;}برای مجموعه‌ای از کارها از awaitAll() استفاده کنیدsuspend fun loadMultipleUsers(userIds: List&lt;String&gt;): List&lt;User&gt; {&#xA;    return userIds.map { userId -&gt;&#xA;        async { userRepository.getUser(userId) }&#xA;    }.awaitAll()&#xA;}نتیجه‌گیریتفاوت launch و async فقط یک قاعده فنی نیست — مستقیماً روی تجربه کاربری تاثیر می‌گذارد. کاربر شاید نداند شما از coroutine استفاده کرده‌اید، اما قطعاً حس می‌کند وقتی صفحه به جای ۳ ثانیه، در ۱ ثانیه لود می‌شود.یادتان باشد:launch برای کارهایی که نتیجه لازم نداریدasync برای مواقعی که نتیجه می‌خواهید یا می‌توانید کارها را همزمان انجام دهیدخطاها را حتماً مدیریت کنیدawait() را آخر کار صدا بزنید</description><category>هادی دهقانی</category><author>هادی دهقانی</author><pubDate>Sat, 20 Sep 2025 15:47:04 +0330</pubDate></item><item><title>انتخاب هوشمندانه Dispatcher در Kotlin Coroutines</title><link>https://virgool.ir/@hadi.dehghani.t/انتخاب-هوشمندانه-dispatcher-در-kotlin-coroutines-ou5vdtn3r9ao</link><description>Smart Dispatcher Selection in Kotlin Coroutinesمقدمه: چرا انتخاب درست Dispatcher حیاتی است؟یکی از مهمترین عواملی که میتواند عملکرد اپلیکیشن اندروید یا سرویس بکاند شما را بهبود دهد، انتخاب صحیح Dispatcher در Kotlin Coroutines است.بسیاری از برنامهنویسان اندروید، به محض نیاز به اجرای کدی در بکگراند، از Dispatchers.IO استفاده میکنند. این کار همیشه بهترین انتخاب نیست و حتی ممکن است باعث کندی عملکرد، مصرف بالای منابع و فریز شدن UI شود.در این مقاله یاد میگیریم:Dispatcher چیست؟انواع آن در Kotlinچه زمانی از Main، Default یا IO استفاده کنیماشتباهات رایج و بهترین شیوههاDispatcher در Kotlin چیست؟Dispatcher در Kotlin Coroutines تعیین میکند که کد شما در کدام Thread یا Thread Pool اجرا شود. به زبان ساده:Dispatcher = مکان اجرای coroutine شماانواع اصلی DispatchersDispatchers.Mainکاربرد: بروزرسانی UI، تعامل با ViewThread: تنها یک Main/UI Threadقابل استفاده در:Android، Swing، JavaFX✅ استفاده صحیح:viewModelScope.launch(Dispatchers.Main) {&#xA;    textView.text = &#34;سلام&#34;&#xA;    progressBar.isVisible = true&#xA;}❌ استفاده نادرست:viewModelScope.launch(Dispatchers.Main) {&#xA;    val result = complexCalculation() // عملیات سنگین روی UI&#xA;    textView.text = result&#xA;}Dispatchers.Defaultکاربرد: عملیات CPU-bound (محاسبات، پردازش داده)Thread Pool: به اندازه هستههای CPUبهینه برای: Sorting، Parsing JSON، پردازش تصویرمثال:suspend fun parseJson(jsonString: String): MyDataModel =&#xA;    withContext(Dispatchers.Default) {&#xA;        Json.decodeFromString&lt;MyDataModel&gt;(jsonString)&#xA;    }Dispatchers.IOکاربرد: عملیات Blocking I/OThread Pool: تا 64 Threadبهینه برای: خواندن/نوشتن فایل، JDBC، درخواست شبکه بدون async✅ استفاده صحیح:suspend fun readFile(file: File): String =&#xA;    withContext(Dispatchers.IO) {&#xA;        file.readText()&#xA;    }❌ استفاده نادرست:suspend fun wrongRetrofitCall() =&#xA;    withContext(Dispatchers.IO) { retrofitApi.getData() } // غیرضروریDispatchers.Unconfinedکاربرد: تنها برای Testing یا موارد خاصویژگی: ثابت نبودن Thread بعد از suspensionهشدار: در Production استفاده نشودمثال:@Test&#xA;fun testExample() = runTest {&#xA;    launch(Dispatchers.Unconfined) {&#xA;        assertEquals(4, 2 + 2)&#xA;    }&#xA;}چرا نباید همیشه از IO استفاده کنیم؟Thread زیاد = کارایی پایین برای تسکهای CPU-boundContext Switching اضافی و کاهش سرعت اجراکتابخانههایی که خودشان Dispatcher دارندRetrofit + OkHttpRoomKtor Clientنتیجه: اگر کتابخانه Thread Management دارد، نیازی به withContext(Dispatchers.IO) نیست.جدول انتخاب Dispatcher مناسبجدول انتخاب Dispatcher مناسبمثال واقعی در ViewModelfun loadProfile(userId: String) = viewModelScope.launch {&#xA;    try {&#xA;        _uiState.value = UiState.Loading&#xA;&#xA;        // Retrofit -&gt; بدون Dispatcher&#xA;        val userData = api.getUserProfile(userId)&#xA;        // Room -&gt; بدون Dispatcher&#xA;        dao.insert(userData.toEntity())&#xA;&#xA;        // پردازش تصویر -&gt; Default&#xA;        val avatar = withContext(Dispatchers.Default) {&#xA;            imageProcessor.applyCircularMask(userData.avatar)&#xA;        }&#xA;&#xA;        // خواندن فایل -&gt; IO&#xA;        val settingsJson = withContext(Dispatchers.IO) {&#xA;            File(&#34;${context.filesDir}/prefs.json&#34;).readText()&#xA;        }&#xA;&#xA;        // پارس کردن جیسون -&gt; Default&#xA;        val settings = withContext(Dispatchers.Default) {&#xA;            Json.decodeFromString&lt;UserSettings&gt;(settingsJson)&#xA;        }&#xA;&#xA;        // آپدیت UI -&gt; Main (پیشفرض در viewModelScope.launch)&#xA;        _uiState.value = UiState.Success(userData, avatar, settings)&#xA;&#xA;    } catch (e: Exception) {&#xA;        _uiState.value = UiState.Error(e.message)&#xA;    }&#xA;}بهترین شیوهها (Best Practices)۱. استفاده از Extension Functionssuspend fun &lt;T&gt; onDefault(block: suspend () -&gt; T) =&#xA;    withContext(Dispatchers.Default) { block() }&#xA;&#xA;suspend fun &lt;T&gt; onIO(block: suspend () -&gt; T) =&#xA;    withContext(Dispatchers.IO) { block() }۲. محدود کردن IOبرای جلوگیری از ایجاد Thread بیش از حد، میتوانید تعداد آنها را محدود کنید:val limitedIO = Dispatchers.IO.limitedParallelism(10)۳. اجتناب از تغییرات تو در توی Dispatcher// خوب&#xA;val data = withContext(Dispatchers.IO) { loadData() }&#xA;val processed = withContext(Dispatchers.Default) { process(data) }&#xA;save(processed) // بدون withContext اضافی❌ اشتباهات رایجاستفاده از Dispatchers.IO برای همه عملیاتهااستفاده از withContext برای کتابخانههایی که Dispatcher داخلی دارند (مثل Retrofit و Room)تودرتو کردن بیدلیل withContext📌 نتیجهگیریMain: فقط و فقط برای UIDefault: برای عملیات سنگین و محاسباتی CPUIO: برای عملیات ورودی/خروجی (I/O) که Thread را مسدود میکنندبدون Dispatcher اضافی برای کتابخانههایی که خودشان Thread Pool دارندبا انتخاب صحیح Dispatcher، اپلیکیشن شما پاسخگوتر، سریعتر و بهینهتر کار خواهد کرد.</description><category>هادی دهقانی</category><author>هادی دهقانی</author><pubDate>Fri, 12 Sep 2025 12:17:52 +0330</pubDate></item></channel></rss>