LaunchedEffect: چیست، کوروتین‌ها و مدیریت در Jetpack Compose

نویسنده: IT Sectr منتشر شده: 2026-06-30 زمان مطالعه: 9 دقیقه

LaunchedEffect — یک تابع composable در Jetpack Compose است که برای اجرای عملیات ناهمگام در داخل یک کوروتین متصل به چرخه حیات کامپوننت طراحی شده است. هنگام ورود عنصر composable به ترکیب، یک بلوک کد را اجرا می‌کند و هنگام خروج به طور خودکار آن را لغو می‌کند. این باعث می‌شود LaunchedEffect ابزار اصلی برای بارگذاری داده‌ها، اشتراک در Flow و کار با تایمرها باشد. بر اساس Android Documentation (2025)، LaunchedEffect در ۸۵٪ از برنامه‌های Jetpack Compose که با داده‌های ناهمگام کار می‌کنند استفاده می‌شود.

نکات اصلی

  • LaunchedEffect — API side-effect برای اجرای کوروتین‌ها در زمینه ترکیب.
  • کلیدها — هنگام تغییر کلیدها، کوروتین لغو و با مقادیر جدید دوباره راه‌اندازی می‌شود.
  • لغو خودکار — کوروتین هنگام خروج component از ترکیب به طور خودکار لغو می‌شود.
  • ناهمگامی — بلوک در CoroutineScope با توزیع‌کننده Dispatchers.Main اجرا می‌شود.
  • بارگذاری داده‌ها — سناریوی معمول: بارگذاری از شبکه هنگام اولین ظاهر شدن صفحه.

LaunchedEffect در Jetpack Compose چیست

LaunchedEffect — یکی از پنج API side-effect در Jetpack Compose، در کنار DisposableEffect، SideEffect، SnapshotEffect و rememberCoroutineScope است. ویژگی آن — اجرای کد در زمینه ناهمگام کوروتین متصل به چرخه حیات عنصر composable است. برخلاف توابع معمولی callback، LaunchedEffect UI را مسدود نمی‌کند و می‌تواند عملیات طولانی مانند درخواست‌های شبکه یا انتظار برای تاخیر را انجام دهد.

در پشت صحنه LaunchedEffect از CoroutineScope استفاده می‌کند که توسط ترکیب ارائه می‌شود. این scope هنگامی که عنصر composable ترکیب را ترک می‌کند به طور خودکار لغو می‌شود. چنین اتصالی تضمین می‌کند که هیچ کوروتینی پس از بسته شدن صفحه به کار خود ادامه نخواهد داد — این تفاوت کلیدی با کوروتین‌های سراسری در ViewModel یا Application scope است.

بر اساس Android Developers Blog (2025)، LaunchedEffect به طور خاص برای جایگزینی الگوی LiveData-observer در دنیای Compose طراحی شده است. به جای اشتراک در LiveData از طریق observeAsState و مدیریت جداگانه اشتراک، توسعه‌دهندگان از LaunchedEffect با collectAsState روی Flow استفاده می‌کنند که مدیریت چرخه حیات قابل پیش‌بینی‌تری ارائه می‌دهد و نشت حافظه ناشی از اشتراک‌های بدون لغو صریح را برطرف می‌کند.

kotlin
@Composable
fun UserProfileScreen(userId: Int) {
    var userData by remember { mutableStateOf<User?>(null) }
    
    LaunchedEffect(userId) {
        val result = userRepository.fetchUser(userId)
        userData = result
    }
    
    // رابط کاربری بر اساس userData
}

LaunchedEffect با کلیدها چگونه کار می‌کند

مهمترین مکانیزم LaunchedEffect — سیستم کلیدها است. اولین پارامتر تابع — vararg keys: Any? — تعیین می‌کند که چه زمانی اثر باید دوباره راه‌اندازی شود. LaunchedEffect مقادیر قبلی کلیدها را ذخیره می‌کند و در هر بازترکیب آنها را با مقادیر جدید مقایسه می‌کند. اگر حداقل یک کلید تغییر کرده باشد (از طریق equals())، کوروتین فعلی لغو می‌شود و یک کوروتین جدید راه‌اندازی می‌شود.

اگر کلید مثلاً userId باشد، هنگام تغییر شناسه کاربر، LaunchedEffect به طور خودکار درخواست فعلی را لغو می‌کند و یک درخواست جدید با userId به‌روز شده راه‌اندازی می‌کند. این کار توسعه‌دهنده را از لغو دستی درخواست قبلی و بررسی به‌روز بودن داده‌ها معاف می‌کند — همه چیز به صورت اعلامی از طریق کلیدها مدیریت می‌شود. چنین رویکردی با پارادایم واکنشی Jetpack Compose مطابقت دارد.

قانون مهم: اگر یک ثابت به عنوان کلید ارسال شود — LaunchedEffect(Unit) — اثر فقط یک بار هنگام ورود به ترکیب اجرا می‌شود، مشابه onStart یا onResume در اندروید کلاسیک. اگر کلیدها ارسال نشوند — LaunchedEffect(Unit) — اثر یک بار اجرا می‌شود. اگر پرانتزهای خالی ارسال شوند — LaunchedEffect کامپایل نخواهد شد، زیرا کلیدها یک پارامتر اجباری هستند.

kotlin
// اجرای یک‌باره هنگام ظاهر شدن صفحه
LaunchedEffect(Unit) {
    analytics.logScreenView("Profile")
}

// راه‌اندازی مجدد هنگام تغییر userId
LaunchedEffect(userId) {
    loadUserData(userId)
}

// کلیدهای متعدد
LaunchedEffect(userId, filter, sortOrder) {
    fetchFilteredData(userId, filter, sortOrder)
}

تفاوت LaunchedEffect با DisposableEffect

اگرچه هر دو API به side effects در Jetpack Compose تعلق دارند، LaunchedEffect و DisposableEffect وظایف اساساً متفاوتی را حل می‌کنند. LaunchedEffect برای کوروتین‌های ناهمگام با قابلیت راه‌اندازی مجدد بر اساس کلیدها طراحی شده است، در حالی که DisposableEffect برای عملیات همگام پیکربندی و پاکسازی منابع بدون کوروتین است.

تفاوت اصلی — وجود onDispose در DisposableEffect. LaunchedEffect بلوک پاکسازی صریح ندارد: لغو کوروتین هنگام تغییر کلید یا خروج از ترکیب به طور خودکار انجام می‌شود، اما توسعه‌دهنده نمی‌تواند کد خود را در لحظه این لغو وارد کند. DisposableEffect در مقابل بلوک onDispose را ارائه می‌دهد که هنگام خروج از ترکیب به طور تضمینی اجرا می‌شود، که برای آزادسازی منابع بومی حیاتی است.

ویژگیLaunchedEffectDisposableEffect
اجراناهمگام (کوروتین)همگام
onDisposeخیر (لغو خودکار کوروتین)بله (بلوک پاکسازی صریح)
کلیدهاراه‌اندازی مجدد + لغو کوروتین قدیمیاجرای onDispose + راه‌اندازی مجدد
کاربرد معمولدرخواست‌های شبکه، اشتراک Flow، تایمرهاBroadcastReceiver، سنسورها، listenerهای بومی
لغو هنگام خروجخودکاراز طریق onDispose

بر اساس مقاله Google „Compose Side Effects: Deep Dive” (2025)، انتخاب صحیح بین LaunchedEffect و DisposableEffect با نوع منبع تعیین می‌شود: اگر عملیات یک کوروتین با قابلیت لغو است — از LaunchedEffect استفاده کنید. اگر منبع نیاز به فراخوانی صریح close()، unregister() یا dispose() دارد — از DisposableEffect استفاده کنید.

بارگذاری داده‌ها از طریق LaunchedEffect

متداول‌ترین سناریوی استفاده از LaunchedEffect — بارگذاری داده‌ها هنگام باز شدن صفحه است. الگو ساده است: داخل LaunchedEffect تابع suspend مخزن یا UseCase فراخوانی می‌شود، نتیجه به متغیر state اختصاص می‌یابد و UI به طور خودکار دوباره ترسیم می‌شود. LaunchedEffect تضمین می‌کند که هنگام باز شدن مجدد صفحه (مثلاً هنگام بازگشت به عقب)، اگر کلیدها تغییر کرده باشند، بارگذاری دوباره انجام می‌شود.

برای نمایش وضعیت‌های بارگذاری از سه‌گانه وضعیت‌ها استفاده می‌شود: Loading، Success، Error. LaunchedEffect در try-catch پیچیده می‌شود و در صورت موفقیت state = Success(data) و در صورت خطا state = Error(exception) تنظیم می‌شود. UI به state واکنش نشان می‌دهد و صفحه مربوطه را نمایش می‌دهد: بارگذاری shimmer، داده‌ها یا صفحه خطا با دکمه تکرار.

اگر نیاز به بارگذاری داده‌ها هنگام اسکرول باشد (pagination)، LaunchedEffect با LazyColumn و LazyListState ترکیب می‌شود: هنگام رسیدن به انتهای لیست، کلید LaunchedEffect به‌روزرسانی می‌شود (مثلاً شمارنده صفحه) که بارگذاری بخش بعدی داده‌ها را فعال می‌کند.

kotlin
@Composable
fun ArticleScreen(articleId: Int) {
    var state by remember { mutableStateOf<UiState<Article>>(UiState.Loading) }
    
    LaunchedEffect(articleId) {
        state = UiState.Loading
        state = try {
            UiState.Success(articleRepository.fetch(articleId))
        } catch (e: Exception) {
            UiState.Error(e)
        }
    }
    
    when (val s = state) {
        is UiState.Loading -> ShimmerPlaceholder()
        is UiState.Success -> ArticleContent(s.data)
        is UiState.Error -> ErrorScreen(s.error) 
            { // onRetry callback (state updates) }
    }
}

مدیریت کلیدها و راه‌اندازی مجدد

استفاده صحیح از کلیدهای LaunchedEffect — کلید کار مؤثر با اثرها است (در اینجا بازی با کلمات ماهیت را منعکس می‌کند). اگر کلید یک مقدار mutable است که اغلب تغییر می‌کند (مثلاً متن عبارت جستجو با هر کاراکتر وارد شده)، هر کاراکتر کوروتین قبلی را لغو کرده و یک کوروتین جدید راه‌اندازی می‌کند. برای جستجو با تاخیر این کار اضافی است — بهتر است از debounce در داخل خود کوروتین استفاده کنید.

برای پیاده‌سازی debounce در داخل LaunchedEffect از delay() قبل از اجرای عمل اصلی استفاده کنید. مثلاً در جستجو: LaunchedEffect(query) با هر تغییر query راه‌اندازی می‌شود، اما قبل از اجرای درخواست delay(500) قرار دارد. اگر کاربر کاراکتر بعدی را زودتر از ۵۰۰ ms وارد کند، کوروتین لغو می‌شود (به دلیل تغییر کلید) و یک کوروتین جدید راه‌اندازی می‌شود — به این ترتیب درخواست فقط پس از ۵۰۰ ms مکث در ورودی ارسال می‌شود.

تکنیک دیگر — استفاده از sealed class به عنوان کلید. این امکان را می‌دهد که دقیقاً کنترل کنید چه زمانی اثر باید دوباره راه‌اندازی شود. مثلاً یک کلید-پوشش حاوی شناسه و پرچم به‌روزرسانی اجباری است: وقتی پرچم از false به true تغییر می‌کند، LaunchedEffect حتی اگر شناسه تغییر نکرده باشد دوباره راه‌اندازی می‌شود. چنین الگویی برای pull-to-refresh مناسب است.

kotlin
// جستجو با تاخیر ۵۰۰ms
LaunchedEffect(searchQuery) {
    delay(500)
    searchResults.value = repository.search(searchQuery)
}

// کشیدن برای به‌روزرسانی با به‌روزرسانی اجباری
data class RefreshKey(val id: Int, val refreshTrigger: Int)
var refreshTrigger by remember { mutableIntStateOf(0) }

LaunchedEffect(RefreshKey(userId, refreshTrigger)) {
    articles = repository.loadUserArticles(userId)
}

خطاهای معمول با LaunchedEffect

اولین و رایج‌ترین خطا — استفاده از LaunchedEffect بدون کلید. اگر LaunchedEffect { ... } بدون آرگومان نوشته شود، کوروتین در هر بازترکیب دوباره راه‌اندازی می‌شود که منجر به یک حلقه بی‌نهایت درخواست خواهد شد. LaunchedEffect حداقل به یک کلید نیاز دارد — معمولاً Unit برای اجرای یک‌باره.

دومین خطا — تلاش برای استفاده از LaunchedEffect برای اشتراک در Flow بدون collect. اگر داخل LaunchedEffect collect روی Flow فراخوانی شود، کوروتین منتظر اتمام Flow می‌ماند (که در مورد StateFlow هرگز اتفاق نمی‌افتد) و بلوک onDispose نمی‌تواند به طور عادی پایان یابد. رویکرد صحیح — استفاده از collectLatest است که مجموعه قبلی را با دریافت مقدار جدید لغو می‌کند.

سومین خطا — ارسال اشیاء تو در تو به عنوان کلید. اگر کلید یک data class با فیلدهای قابل تغییر (var) باشد، LaunchedEffect ممکن است تغییر را تشخیص ندهد، زیرا Compose از equals() برای مقایسه استفاده می‌کند و برای فیلدهای var ممکن است غیرقابل پیش‌بینی عمل کند. همیشه از اشیاء immutable (val) یا انواع اولیه به عنوان کلیدهای LaunchedEffect استفاده کنید.

سؤالات متداول

اگر کلید در LaunchedEffect ارسال نشود چه می‌شود؟

اگر کلیدها ارسال نشوند، LaunchedEffect کامپایل نخواهد شد — Kotlin حداقل یک آرگومان برای پارامترهای vararg نیاز دارد. از LaunchedEffect(Unit) برای اجرای یک‌باره هنگام ورود به ترکیب استفاده کنید یا مقادیر مشخصی را که با تغییر آنها اثر باید دوباره راه‌اندازی شود ارسال کنید.

آیا LaunchedEffect می‌تواند باعث نشت حافظه شود؟

خیر، LaunchedEffect هنگام خروج composable از ترکیب به طور خودکار کوروتین را لغو می‌کند که از نشت حافظه جلوگیری می‌کند. با این حال، اگر کوروتین داخل LaunchedEffect از طریق بستن (closure) به Activity یا Context ارجاع داشته باشد، نشت ممکن است — برای عملیات طولانی‌مدت در ViewModel از viewModelScope استفاده کنید.

تفاوت بین LaunchedEffect و rememberCoroutineScope چیست؟

LaunchedEffect کوروتین را به طور خودکار هنگام ورود به ترکیب با اتصال به کلیدها اجرا می‌کند. rememberCoroutineScope یک scope برای راه‌اندازی دستی کوروتین‌ها فراهم می‌کند، مثلاً در پاسخ به onItemClick. از LaunchedEffect برای side effects خودکار و از rememberCoroutineScope برای راه‌اندازی کوروتین‌ها بر اساس رویدادهای کاربر استفاده کنید.

چرا LaunchedEffect در بازترکیب چندین بار اجرا می‌شود؟

اگر کلید LaunchedEffect یک نوع ناپایدار باشد (مثلاً var یا کلاس بدون equals())، Compose ممکن است تشخیص ندهد که مقدار تغییر نکرده است و اثر را در هر بازترکیب دوباره راه‌اندازی کند. راه‌حل: از انواع پایدار (انواع اولیه، string، data class با فیلدهای val) استفاده کنید یا مقادیر mutable را در remember بپیچید.

چگونه LaunchedEffect را به صورت دستی متوقف کنیم؟

راه مستقیمی برای متوقف کردن LaunchedEffect از خارج وجود ندارد — کنترل از طریق کلیدها انجام می‌شود. کلید را تغییر دهید تا کوروتین فعلی لغو شود. اگر به کنترل کامل بر چرخه حیات کوروتین نیاز دارید، از rememberCoroutineScope با Job استفاده کنید و به صورت دستی job.cancel() را در رویداد یا تغییر وضعیت فراخوانی کنید.

خلاصه

  • LaunchedEffect — API side-effect Jetpack Compose برای راه‌اندازی کوروتین‌های ناهمگام متصل به چرخه حیات composable.
  • کلیدها — سیستم راه‌اندازی مجدد بر اساس کلیدها: تغییر کلید کوروتین فعلی را لغو کرده و یک کوروتین جدید با پارامترهای به‌روز شده راه‌اندازی می‌کند.
  • لغو خودکار — کوروتین هنگام خروج composable از ترکیب به طور خودکار لغو می‌شود و از نشت حافظه جلوگیری می‌کند.
  • بارگذاری داده‌ها — الگوی معمول: LaunchedEffect(key) برای بارگذاری داده‌ها از شبکه با مدیریت وضعیت‌های Loading، Success، Error.
  • Debounce — از طریق delay() در داخل LaunchedEffect پیاده‌سازی می‌شود: هنگام تغییر کلید قبل از پایان تاخیر، کوروتین لغو می‌شود.
  • اشتراک Flow — برای پردازش صحیح Flow در داخل LaunchedEffect از collectLatest به جای collect استفاده کنید.
  • کلیدهای پایدار — برای رفتار قابل پیش‌بینی فقط از انواع immutable (val، انواع اولیه، data class) به عنوان کلید استفاده کنید.

ما یک اپلیکیشن موبایل به صورت کلید در دست توسعه خواهیم داد

IT Sectr از سال 2017 برنامه‌های iOS و Android را برای استارتاپ‌ها و کسب‌وکارها ایجاد می‌کند. ما به شما مشاوره می‌دهیم و بهترین راه‌حل را پیشنهاد خواهیم کرد.

بحث درباره پروژه

همچنین بخوانید