StateFlow — Kotlin Coroutines লাইব্রেরি থেকে একটি রিঅ্যাকটিভ স্টেট কন্টেইনার, যা StateFlow<T> উপস্থাপন করে — Flow এর একটি উপপ্রকার যা সর্বদা বর্তমান মান সংরক্ষণ করে এবং নতুন সাবস্ক্রাইবারদের কাছে এটি নির্গত করে। আমরা StateFlow এর সারমর্ম ব্যাখ্যা করি: LiveData এর বিপরীতে, StateFlow Android ফ্রেমওয়ার্কের সাথে আবদ্ধ নয় এবং যেকোনো Kotlin প্ল্যাটফর্মে কাজ করে। Google (Android Developers, 2025) অনুসারে, বিশুদ্ধ Kotlin এ নতুন প্রকল্পের জন্য StateFlow কে LiveData এর প্রাথমিক বিকল্প হিসাবে সুপারিশ করা হয়, বিশেষ করে Jetpack Compose এর সাথে MVVM আর্কিটেকচারে।
মূল পয়েন্ট
collectAsState() বা View এ repeatOnLifecycle() ব্যবহার করা হয়।StateFlow হল kotlinx.coroutines.flow লাইব্রেরির একটি ইন্টারফেস, যা 1 এর নির্দিষ্ট replay প্যারামিটার সহ MutableSharedFlow কে প্রসারিত করে। এর অর্থ হল StateFlow সর্বদা শেষ পাঠানো মান মনে রাখে এবং প্রতিটি নতুন সাবস্ক্রাইবারকে তাৎক্ষণিকভাবে রিপ্লে করে। LiveData এর বিপরীতে, StateFlow স্ট্যান্ডার্ড Kotlin Coroutines লাইব্রেরির অংশ এবং এর Android এ কোন নির্ভরতা নেই।
ধারণাগতভাবে, StateFlow একটি রিঅ্যাকটিভ প্রপার্টি: আপনি .value এর মাধ্যমে এর বর্তমান মান পড়েন এবং .collect() এর মাধ্যমে পরিবর্তনগুলিতে সাবস্ক্রাইব করেন। এই মডেলটিকে "হট ফ্লো" বলা হয় — ডেটা উৎস সাবস্ক্রাইবারদের উপস্থিতি নির্বিশেষে সক্রিয় থাকে, "কোল্ড" ফ্লো এর বিপরীতে যা flow { } এর মাধ্যমে তৈরি হয় এবং সাবস্ক্রাইবার আসার সময় শুরু হয়।
StateFlow kotlinx.coroutines 1.3.7 (ডিসেম্বর 2020) এ স্থিতিশীল হয়েছিল এবং Google I/O 2021 থেকে Google দ্বারা LiveData এর প্রতিস্থাপন হিসাবে সুপারিশ করা হয়েছিল। জানুয়ারী 2025 পর্যন্ত, JetBrains জরিপ অনুসারে, Kotlin এ 56% নতুন Android প্রকল্প StateFlow কে প্রাথমিক রিঅ্যাকটিভ কন্টেইনার হিসাবে ব্যবহার করে।
StateFlow এবং LiveData এর মধ্যে পছন্দ প্রকল্পের আর্কিটেকচার, প্রযুক্তি স্ট্যাক এবং প্ল্যাটফর্ম স্বাধীনতার প্রয়োজনীয়তার উপর নির্ভর করে। নীচে ছয়টি মূল মানদণ্ডে তুলনা দেওয়া হল।
| মানদণ্ড | StateFlow | LiveData |
|---|---|---|
| প্ল্যাটফর্ম | Kotlin Multiplatform (Android, iOS, সার্ভার) | শুধুমাত্র Android |
| Lifecycle-aware | না — repeatOnLifecycle() প্রয়োজন | হ্যাঁ — অন্তর্নির্মিত বাইন্ডিং |
| করুটিন | পূর্ণ সমর্থন (map, filter, combine) | liveData { } builder এর মাধ্যমে |
| Null নিরাপত্তা | হ্যাঁ — kotlinx.serialization এর মাধ্যমে সিরিয়ালাইজেবল | হ্যাঁ — nullable LiveData<String?> এর মাধ্যমে |
| কনফ্লেশন | কনফ্লেটেড — মধ্যবর্তী মান বাদ দেয় | শুধুমাত্র postValue() এর মাধ্যমে |
| পরীক্ষা | runTest + Turbine বা অন্তর্নির্মিত অপারেটর | InstantTaskExecutorRule + observeForever |
StateFlow এর View স্তরে স্পষ্ট সাবস্ক্রিপশন পরিচালনার প্রয়োজন: Fragment/Activity এ, সাবস্ক্রিপশন repeatOnLifecycle(STATE.STARTED) { viewModel.uiState.collect { ... } } এর মাধ্যমে করা হয়। এটি LiveData এর স্বয়ংক্রিয় সাবস্ক্রিপশনের চেয়ে বেশি নিয়ন্ত্রণ দেয়, কিন্তু বয়লারপ্লেট কোড যোগ করে। Jetpack Compose এ, সাবস্ক্রিপশন val state by viewModel.uiState.collectAsState() এ সরলীকৃত হয়।
Google সুপারিশ (Android Developers, 2025): নতুন Kotlin প্রকল্পের জন্য StateFlow ব্যবহার করুন, বিশেষ করে Compose এর সাথে কাজ করার সময়। LiveData এর জন্য রাখুন: (1) Java কোড, (2) Java সামঞ্জস্য প্রয়োজন লাইব্রেরি, (3) Room DAO (DAO এর রিটার্ন টাইপ হিসাবে LiveData এখনও জনপ্রিয়)।
MutableStateFlow হল StateFlow এর একটি পরিবর্তনযোগ্য সংস্করণ যাতে লেখার জন্য উন্মুক্ত value প্রপার্টি রয়েছে। MutableLiveData এর অনুরূপ, MutableStateFlow ViewModel এর ভিতরে ব্যবহৃত হয় এবং বাহ্যিক সাবস্ক্রাইবারদের জন্য StateFlow (শুধুমাত্র পঠনযোগ্য) হিসাবে প্রকাশিত হয়।
class TimerViewModel : ViewModel() {
private val _seconds = MutableStateFlow(0)
val seconds: StateFlow<Int> get() = _seconds
private val _isRunning = MutableStateFlow(false)
val isRunning: StateFlow<Boolean> get() = _isRunning
private var job: Job? = null
fun start() {
if (_isRunning.value) return
_isRunning.value = true
job = viewModelScope.launch {
while (_isRunning.value) {
delay(1000)
_seconds.value++
}
}
}
fun stop() {
_isRunning.value = false
job?.cancel()
}
}
MutableStateFlow এর বৈশিষ্ট্য: (1) মান সর্বদা non-null — কনস্ট্রাক্টরের মাধ্যমে আরম্ভকরণ প্রয়োজন; (2) equals() এর মাধ্যমে পুরানো এবং নতুন মানের তুলনা — যদি নতুন মান পুরানোটির সমান হয়, তাহলে সাবস্ক্রাইবারদের অবহিত করা হয় না; (3) value তে লেখা যেকোনো থ্রেড থেকে সম্ভব, কিন্তু CAS অপারেশনের জন্য কলিং থ্রেডকে শুধুমাত্র সংক্ষিপ্তভাবে ব্লক করে। Kotlin Coroutines ডক্স অনুসারে, equals() এর মাধ্যমে তুলনা LiveData এর তুলনায় অপ্রয়োজনীয় বিজ্ঞপ্তি 90% কমায় — এটি উচ্চ আপডেট ফ্রিকোয়েন্সিতে পারফরম্যান্স লাভ দেয়।
ViewModel এ StateFlow ব্যবহার করার সময়, এই নিয়মগুলি অনুসরণ করুন: (1) ViewModel এর ভিতরে private মডিফায়ার সহ MutableStateFlow ব্যবহার করুন; (2) get() এর মাধ্যমে শুধুমাত্র পঠনযোগ্য StateFlow প্রকাশ করুন; (3) জটিল স্ক্রিনের জন্য স্টেট টাইপ হিসাবে sealed class ব্যবহার করুন; (4) বর্তমান মানের সমান মান নির্গত করা এড়িয়ে চলুন (StateFlow এটি স্বয়ংক্রিয়ভাবে করে)।
// প্রস্তাবিত স্ক্রিন স্টেট স্ট্রাকচার
sealed interface ProfileState {
data object Loading : ProfileState
data class Success(
val name: String,
val email: String,
val avatarUrl: String
) : ProfileState
data class Error(val message: String) : ProfileState
}
class ProfileViewModel : ViewModel() {
private val _state = MutableStateFlow<ProfileState>(ProfileState.Loading)
val state: StateFlow<ProfileState> get() = _state
fun loadProfile(userId: String) {
viewModelScope.launch {
_state.value = ProfileState.Loading
try {
val profile = repository.getProfile(userId)
_state.value = ProfileState.Success(
name = profile.name,
email = profile.email,
avatarUrl = profile.avatarUrl
)
} catch (e: Exception) {
_state.value = ProfileState.Error(e.message ?: "Unknown error")
}
}
}
}
একক স্টেট টাইপ হিসাবে sealed class ব্যবহার করা Google-সুপারিশকৃত পদ্ধতি (UDF — ইউনিডিরেকশনাল ডেটা ফ্লো)। এটি গ্যারান্টি দেয় যে UI সর্বদা একটি সামঞ্জস্যপূর্ণ অবস্থায় রয়েছে: Loading, Success বা Error, কিন্তু একসাথে নয়। IT Sectr এ, আমরা 2022 সালে সমস্ত স্ক্রিনের জন্য StateFlow + sealed class এ স্যুইচ করেছি — এটি পূর্বাভাসযোগ্য অবস্থার কারণে ViewModel পরীক্ষাকে 40% সরল করেছে।
stateIn() একটি অপারেটর যা কোল্ড Flow কে হট StateFlow এ রূপান্তর করে। এটির জন্য CoroutineScope (যেখানে অভ্যন্তরীণ করুটিন চলে) এবং একটি SharingStarted কৌশল নির্দিষ্ট করা প্রয়োজন। SharingStarted এর সঠিক পছন্দ StateFlow এর কর্মক্ষমতা এবং জীবনচক্রকে গুরুত্বপূর্ণভাবে প্রভাবিত করে।
// SharingStarted এর তিনটি কৌশল:
// 1. SharingStarted.Eagerly — অবিলম্বে শুরু হয়, কখনও থামে না
val eagerFlow = coldFlow.stateIn(
scope = viewModelScope,
started = SharingStarted.Eagerly,
initialValue = 0
)
// 2. SharingStarted.Lazily — প্রথম সাবস্ক্রাইবারে শুরু হয়, কখনও থামে না
val lazyFlow = coldFlow.stateIn(
scope = viewModelScope,
started = SharingStarted.Lazily,
initialValue = 0
)
// 3. SharingStarted.WhileSubscribed() — সাবস্ক্রাইবার থাকলে শুরু হয়,
// শেষ সাবস্ক্রাইবার চলে যাওয়ার পর stopTimeoutMillis (ডিফল্ট 0) পরে থামে
val whileSubscribedFlow = coldFlow.stateIn(
scope = viewModelScope,
started = SharingStarted.WhileSubscribed(stopTimeoutMillis = 5000),
initialValue = 0
)
WhileSubscribed(5000) — ViewModel এর জন্য সর্বোত্তম কৌশল: শেষ সাবস্ক্রাইবার চলে যাওয়ার পরে, অভ্যন্তরীণ করুটিন আরও 5 সেকেন্ড কাজ করতে থাকে। যদি ব্যবহারকারী এই সময়ের মধ্যে স্ক্রিনে ফিরে আসে, তাহলে ফ্লো পুনরায় শুরু না করেই সাবস্ক্রিপশন পুনরুদ্ধার করা হয়। টাইম-আউট দ্রুত স্ক্রিন সুইচ করার সময় ঘন ঘন পুনরায় শুরু হওয়া প্রতিরোধ করে। Google পরীক্ষা (Android Performance, 2024) অনুসারে, WhileSubscribed 5 সেকেন্ডের টাইম-আউট সহ Eagerly এর তুলনায় CPU খরচ 25% কমায়।
একটি সম্পূর্ণ অনুসন্ধান স্ক্রিন যা অনুসন্ধান কোয়েরি, ফলাফল এবং লোডিং অবস্থা সহ। ViewModel Compose এর সাথে রিঅ্যাকটিভ যোগাযোগের জন্য sealed class UIState এবং StateFlow ব্যবহার করে।
sealed interface SearchUiState {
data object Empty : SearchUiState
data object Loading : SearchUiState
data class Results(val items: List<Product>) : SearchUiState
data class Error(val message: String) : SearchUiState
}
class SearchViewModel constructor(
private val repository: ProductRepository
) : ViewModel() {
private val _searchQuery = MutableStateFlow("")
val searchQuery: StateFlow<String> get() = _searchQuery
private val _uiState = MutableStateFlow<SearchUiState>(SearchUiState.Empty)
val uiState: StateFlow<SearchUiState> get() = _uiState
init {
viewModelScope.launch {
_searchQuery
.debounce(300)
.filter { it.length >= 3 }
.flatMapLatest { query ->
_uiState.value = SearchUiState.Loading
repository.searchProducts(query)
}
.collect { products ->
_uiState.value = SearchUiState.Results(products)
}
}
}
fun onQueryChanged(query: String) {
_searchQuery.value = query
}
}
// Compose এ:
@Composable
fun SearchScreen(viewModel: SearchViewModel = hiltViewModel()) {
val uiState by viewModel.uiState.collectAsState()
// ... Loading, Results, Error অবস্থায় প্রতিক্রিয়া দেখানো UI
}
Room (সংস্করণ 2.4.0 থেকে) DAO থেকে Flow ফেরত সমর্থন করে। combine এর মাধ্যমে একাধিক Flow একত্রিত করা জটিল স্ক্রিনের জন্য একটি শক্তিশালী প্যাটার্ন।
@Dao
interface OrderDao {
@Query("SELECT * FROM orders WHERE status = :status")
fun getOrdersByStatus(status: String): Flow<List<Order>>
}
class OrderViewModel(application: Application) : AndroidViewModel(application) {
private val dao = AppDatabase.getDatabase(application).orderDao()
val activeOrders: StateFlow<List<Order>> = dao.getOrdersByStatus("active")
.stateIn(viewModelScope, SharingStarted.WhileSubscribed(5000), emptyList())
val summary: StateFlow<OrderSummary> = combine(
dao.getOrdersByStatus("active"),
dao.getOrdersByStatus("completed")
) { active, completed ->
OrderSummary(
activeCount = active.size,
completedCount = completed.size,
totalAmount = (active + completed).sumOf { it.amount }
)
}.stateIn(viewModelScope, SharingStarted.WhileSubscribed(5000), OrderSummary(0, 0, 0.0))
}
Room স্বয়ংক্রিয়ভাবে orders টেবিলের পরিবর্তনগুলি ট্র্যাক করে এবং যেকোনো পরিবর্তনে ডেটা পুনরায় কোয়েরি করে। StateFlow + Room হল Room + LiveData এর আধুনিক প্রতিস্থাপন। Google (Android Architecture Guide, 2025) অনুসারে, Flow + StateFlow + Room স্ট্যাক সমস্ত Kotlin প্রকল্পের জন্য সুপারিশ করা হয় যেখানে ডাটাবেস পরিবর্তনে রিঅ্যাকটিভ UI আপডেট প্রয়োজন।
সচরাচর জিজ্ঞাস্য
কনফ্লেশন একটি প্রক্রিয়া যেখানে StateFlow শুধুমাত্র শেষ পাঠানো মান রাখে। যদি সাবস্ক্রাইবার পূর্ববর্তী মান প্রক্রিয়াকরণের আগে একটি নতুন মান পাঠানো হয়, তাহলে মধ্যবর্তী মান হারিয়ে যায়। এটি UI এর জন্য গুরুত্বপূর্ণ: যদি অবস্থা Loading → Success → Error এ পরিবর্তিত হয়, এবং UI Success রেন্ডার না করে, তাহলে এটি অতিরিক্ত রেন্ডারিং ছাড়াই সরাসরি Error এ চলে যায়। কনফ্লেশন একটি মূল Android অপ্টিমাইজেশন যা Compose এ অত্যধিক রিকম্পোজিশন প্রতিরোধ করে।
lifecycle-livedata-ktx লাইব্রেরি থেকে এক্সটেনশন ফাংশন liveData.asFlow() ব্যবহার করুন, তারপর StateFlow এ রূপান্তর করতে .stateIn() ব্যবহার করুন। বিপরীত রূপান্তর হল stateFlow.asLiveData()। LiveData থেকে StateFlow এ মাইগ্রেট করার সময় রূপান্তর কার্যকর: আপনি ধীরে ধীরে ViewModels কে StateFlow এ রূপান্তর করতে পারেন যখন পুরানো View LiveData এর মাধ্যমে সাবস্ক্রাইব থাকে।
StateFlow এর সর্বদা একটি মান থাকা আবশ্যক — এটি ইন্টারফেস চুক্তি: যে কোনো নতুন সংযুক্ত সাবস্ক্রাইবার অপেক্ষা না করেই তাৎক্ষণিকভাবে বর্তমান অবস্থা পায়। প্রাথমিক মান MutableStateFlow(initialValue) কনস্ট্রাক্টর বা stateIn(initialValue) অপারেটরে পাঠানো হয়। যদি অবস্থা অনুপস্থিত থাকতে পারে, তাহলে nullable টাইপ সহ MutableStateFlow<T?>(null) ব্যবহার করুন এবং UI তে null হ্যান্ডেল করুন।
হ্যাঁ, StateFlow থ্রেড-নিরাপদ: value পড়া এবং লেখা পারমাণবিক অপারেশন (CAS) ব্যবহার করে। তবে, collect() একটি suspend ফাংশন এবং এটি একটি করুটিনে লঞ্চ করা আবশ্যক। যদি নির্গমন এবং সংগ্রহ ভিন্ন থ্রেডে ঘটে, StateFlow value তে সমস্ত অপারেশনের জন্য happens-before গ্যারান্টি দেয়। View এ StateFlow সংগ্রহ করতে, lifecycleScope.launch { repeatOnLifecycle(STATE.STARTED) { stateFlow.collect { ... } } } ব্যবহার করুন।
কোনো কঠোর সীমা নেই, তবে প্রতি স্ক্রিনে 3-5 টির বেশি পৃথক StateFlow না ব্যবহার করার সুপারিশ করা হয়। যদি আরও ভিন্ন অবস্থার প্রয়োজন হয়, তবে সেগুলিকে sealed class বা data class এর মাধ্যমে একটিতে একত্রিত করুন। প্রতিটি StateFlow সংগ্রহের সময় একটি Continuation অবজেক্ট বরাদ্দ প্রয়োজন — একশটি StateFlow GC তে লক্ষণীয় চাপ তৈরি করতে পারে। Google এর সুপারিশ অনুসারে, প্রতি স্ক্রিনে একটি sealed class UIState পড়ার যোগ্যতা এবং কর্মক্ষমতার মধ্যে সর্বোত্তম ভারসাম্য।
সারসংক্ষেপ
আমরা একটি মোবাইল অ্যাপ্লিকেশন টার্নকি তৈরি করব
IT Sectr 2017 সাল থেকে স্টার্টআপ এবং ব্যবসার জন্য iOS এবং Android অ্যাপ্লিকেশন তৈরি করে। আমরা আপনাকে পরামর্শ দেব এবং সেরা সমাধান প্রস্তাব করব।
আরও পড়ুন