Last active
August 7, 2025 08:44
-
-
Save mahdiPourkazemi/dbd2f051f36624bcb92a873dc102b416 to your computer and use it in GitHub Desktop.
LaunchedEffect vs rememberCoroutineScope in compose
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
//########LaunchedEffect#################### | |
// first load in page: init vs Launched Effect | |
@Composable | |
fun MyScreen(userId: String) { | |
LaunchedEffect(userId) { | |
// هر بار userId تغییر کنه اجرا میشه | |
val user = userRepository.getUser(userId) | |
println(user.name) | |
} | |
} | |
//############################################################## | |
LaunchedEffect(Unit) { | |
val savedToken = dataStore.getToken() | |
if (savedToken != null) { | |
navigateToHome() | |
} | |
} | |
//############################################################## | |
// if data key come from user use the launchedEffect like this example if not use init{} in viewMOdel | |
// ViewModel - مدیریت State و Business Logic | |
@HiltViewModel | |
class UserProfileViewModel @Inject constructor( | |
private val userRepository: UserRepository | |
) : ViewModel() { | |
private val _uiState = MutableStateFlow(UserProfileUiState()) | |
val uiState = _uiState.asStateFlow() | |
fun loadUser(userId: String) { | |
viewModelScope.launch { | |
_uiState.value = _uiState.value.copy(isLoading = true) | |
try { | |
val user = userRepository.getUser(userId) | |
_uiState.value = _uiState.value.copy( | |
user = user, | |
isLoading = false, | |
error = null | |
) | |
} catch (e: Exception) { | |
_uiState.value = _uiState.value.copy( | |
isLoading = false, | |
error = e.message ?: "خطای نامشخص" | |
) | |
} | |
} | |
} | |
} | |
// UI State Data Class | |
data class UserProfileUiState( | |
val user: User? = null, | |
val isLoading: Boolean = false, | |
val error: String? = null | |
) | |
// User Data Class | |
data class User( | |
val id: String, | |
val name: String, | |
val email: String, | |
val avatarUrl: String? | |
) | |
// Composable - فقط UI و Reactive Calls | |
@Composable | |
fun UserProfileScreen( | |
userId: String, | |
viewModel: UserProfileViewModel = hiltViewModel() | |
) { | |
val uiState by viewModel.uiState.collectAsState() | |
// Reactive call - هر بار userId تغییر کند، دوباره لود میشود | |
LaunchedEffect(userId) { | |
viewModel.loadUser(userId) | |
} | |
Column( | |
modifier = Modifier | |
.fillMaxSize() | |
.padding(16.dp), | |
horizontalAlignment = Alignment.CenterHorizontally | |
) { | |
when { | |
uiState.isLoading -> { | |
CircularProgressIndicator() | |
Text("در حال بارگذاری...") | |
} | |
uiState.error != null -> { | |
Text( | |
text = "خطا: ${uiState.error}", | |
color = MaterialTheme.colorScheme.error | |
) | |
Button( | |
onClick = { viewModel.loadUser(userId) } | |
) { | |
Text("تلاش مجدد") | |
} | |
} | |
uiState.user != null -> { | |
UserProfileContent(user = uiState.user) | |
} | |
} | |
} | |
} | |
@Composable | |
private fun UserProfileContent(user: User) { | |
Column( | |
horizontalAlignment = Alignment.CenterHorizontally, | |
verticalArrangement = Arrangement.spacedBy(16.dp) | |
) { | |
// Avatar | |
AsyncImage( | |
model = user.avatarUrl, | |
contentDescription = "تصویر پروفایل ${user.name}", | |
modifier = Modifier | |
.size(100.dp) | |
.clip(CircleShape), | |
placeholder = painterResource(R.drawable.ic_person) | |
) | |
// Name | |
Text( | |
text = user.name, | |
style = MaterialTheme.typography.headlineMedium, | |
fontWeight = FontWeight.Bold | |
) | |
Text( | |
text = user.email, | |
style = MaterialTheme.typography.bodyLarge, | |
color = MaterialTheme.colorScheme.onSurfaceVariant | |
) | |
// User ID (for demo) | |
Text( | |
text = "شناسه: ${user.id}", | |
style = MaterialTheme.typography.bodySmall, | |
color = MaterialTheme.colorScheme.outline | |
) | |
} | |
} | |
// استفاده در Navigation | |
@Composable | |
fun UserProfileRoute( | |
userId: String, | |
onBackClick: () -> Unit | |
) { | |
Scaffold( | |
topBar = { | |
TopAppBar( | |
title = { Text("پروفایل کاربر") }, | |
navigationIcon = { | |
IconButton(onClick = onBackClick) { | |
Icon(Icons.Default.ArrowBack, "بازگشت") | |
} | |
} | |
) | |
} | |
) { paddingValues -> | |
Box(modifier = Modifier.padding(paddingValues)) { | |
UserProfileScreen(userId = userId) | |
} | |
} | |
} | |
//############################################################## | |
val scope = rememberCoroutineScope() | |
Button(onClick = { | |
scope.launch { | |
val result = longRunningTask() | |
println(result) | |
} | |
}) { | |
Text("Click me") | |
} | |
//############################################################## | |
var isLoading by remember { mutableStateOf(false) } | |
val scope = rememberCoroutineScope() | |
Button( | |
onClick = { | |
if (!isLoading) { | |
isLoading = true | |
scope.launch { | |
val result = longRunningTask() | |
println(result) | |
isLoading = false | |
} | |
} | |
}, | |
enabled = !isLoading | |
) { | |
Text(if (isLoading) "Loading..." else "Click me") | |
} | |
//############################################################## | |
val scope = rememberCoroutineScope() | |
var job by remember { mutableStateOf<Job?>(null) } | |
Button(onClick = { | |
if (job?.isActive != true) { | |
job = scope.launch { | |
longRunningTask() | |
} | |
} | |
}) { | |
Text("Click me") | |
} | |
//############################################################## | |
@Composable | |
fun AsyncButton() { | |
var isLoading by remember { mutableStateOf(false) } | |
val scope = rememberCoroutineScope() | |
Button( | |
onClick = { | |
if (!isLoading) { | |
isLoading = true | |
scope.launch { | |
try { | |
val result = longRunningTask() | |
println("Result: $result") | |
} catch (e: Exception) { | |
println("Error: ${e.message}") | |
// Handle error (show toast, snackbar, etc.) | |
} finally { | |
isLoading = false | |
} | |
} | |
} | |
}, | |
enabled = !isLoading | |
) { | |
if (isLoading) { | |
Row( | |
horizontalArrangement = Arrangement.spacedBy(8.dp), | |
verticalAlignment = Alignment.CenterVertically | |
) { | |
CircularProgressIndicator( | |
modifier = Modifier.size(16.dp), | |
strokeWidth = 2.dp, | |
color = MaterialTheme.colorScheme.onPrimary | |
) | |
Text("Loading...") | |
} | |
} else { | |
Text("Click me") | |
} | |
} | |
} | |
//############################################################## | |
// روش حرفهایتر با ViewModel | |
@Composable | |
fun AsyncButtonWithViewModel(viewModel: MyViewModel) { | |
val uiState by viewModel.uiState.collectAsState() | |
Button( | |
onClick = { viewModel.performLongRunningTask() }, | |
enabled = uiState !is UiState.Loading | |
) { | |
when (uiState) { | |
is UiState.Loading -> { | |
Row( | |
horizontalArrangement = Arrangement.spacedBy(8.dp), | |
verticalAlignment = Alignment.CenterVertically | |
) { | |
CircularProgressIndicator( | |
modifier = Modifier.size(16.dp), | |
strokeWidth = 2.dp | |
) | |
Text("Loading...") | |
} | |
} | |
is UiState.Error -> Text("Retry") | |
else -> Text("Click me") | |
} | |
} | |
} | |
// ViewModel مربوطه | |
class MyViewModel : ViewModel() { | |
private val _uiState = MutableStateFlow<UiState>(UiState.Idle) | |
val uiState = _uiState.asStateFlow() | |
fun performLongRunningTask() { | |
if (_uiState.value is UiState.Loading) return | |
viewModelScope.launch { | |
_uiState.value = UiState.Loading | |
try { | |
val result = longRunningTask() | |
_uiState.value = UiState.Success(result) | |
} catch (e: Exception) { | |
_uiState.value = UiState.Error(e.message ?: "Unknown error") | |
} | |
} | |
} | |
} | |
sealed class UiState { | |
object Idle : UiState() | |
object Loading : UiState() | |
data class Success(val data: String) : UiState() | |
data class Error(val message: String) : UiState() | |
} | |
suspend fun longRunningTask(): String { | |
delay(3000) // شبیهسازی کار زمانبر | |
return "Task completed!" | |
} | |
Author
mahdiPourkazemi
commented
Aug 7, 2025

Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment