UI স্তর (ভিউ)

ধারণা এবং জেটপ্যাক কম্পোজ বাস্তবায়ন

UI-এর ভূমিকা হলো স্ক্রিনে অ্যাপ্লিকেশন ডেটা প্রদর্শন করা এবং ব্যবহারকারীর মিথস্ক্রিয়ার প্রধান কেন্দ্রবিন্দু হিসেবে কাজ করা। যখনই ব্যবহারকারীর মিথস্ক্রিয়া (যেমন একটি বোতাম চাপা) বা বাহ্যিক ইনপুটের (যেমন একটি নেটওয়ার্ক প্রতিক্রিয়া) কারণে ডেটা পরিবর্তিত হয়, সেই পরিবর্তনগুলো প্রতিফলিত করার জন্য UI-এর আপডেট হওয়া উচিত। প্রকৃতপক্ষে, UI হলো ডেটা লেয়ার থেকে প্রাপ্ত অ্যাপ্লিকেশন অবস্থার একটি ভিজ্যুয়াল উপস্থাপনা।

তবে, ডেটা লেয়ার থেকে আপনি যে অ্যাপ্লিকেশন ডেটা পান, তা সাধারণত আপনার প্রদর্শনের জন্য প্রয়োজনীয় তথ্যের চেয়ে ভিন্ন ফরম্যাটে থাকে। উদাহরণস্বরূপ, UI-এর জন্য আপনার হয়তো ডেটার শুধু একটি অংশের প্রয়োজন হতে পারে, অথবা ব্যবহারকারীর জন্য প্রাসঙ্গিক তথ্য উপস্থাপন করতে আপনার দুটি ভিন্ন ডেটা সোর্সকে একত্রিত করার প্রয়োজন হতে পারে। আপনি যে লজিকই প্রয়োগ করুন না কেন, UI-কে সম্পূর্ণরূপে রেন্ডার করার জন্য প্রয়োজনীয় সমস্ত তথ্য আপনাকে সরবরাহ করতে হবে। UI লেয়ার হলো সেই পাইপলাইন যা অ্যাপ্লিকেশন ডেটার পরিবর্তনগুলোকে এমন একটি ফর্মে রূপান্তরিত করে যা UI উপস্থাপন করতে পারে এবং তারপর তা প্রদর্শন করে।

UI অবস্থা প্রকাশ করুন

আপনার UI স্টেট সংজ্ঞায়িত করার পর এবং সেই স্টেটের প্রোডাকশন কীভাবে পরিচালনা করবেন তা নির্ধারণ করার পরে, পরবর্তী ধাপ হলো উৎপাদিত স্টেটটিকে UI-এর কাছে উপস্থাপন করা। যেহেতু আপনি স্টেট প্রোডাকশন পরিচালনা করার জন্য UDF ব্যবহার করছেন, তাই আপনি উৎপাদিত স্টেটটিকে একটি স্ট্রিম হিসেবে বিবেচনা করতে পারেন—অন্য কথায়, সময়ের সাথে সাথে স্টেটের একাধিক সংস্করণ তৈরি হবে। ফলস্বরূপ, আপনার UI স্টেটকে LiveData বা StateFlow মতো একটি অবজার্ভেবল ডেটা হোল্ডারে প্রকাশ করা উচিত। এর কারণ হলো, UI যেন ViewModel থেকে সরাসরি ম্যানুয়ালি ডেটা না টেনেই স্টেটের যেকোনো পরিবর্তনে প্রতিক্রিয়া জানাতে পারে। এই ধরনের ডেটা হোল্ডারগুলোর আরেকটি সুবিধা হলো, এগুলোতে UI স্টেটের সর্বশেষ সংস্করণটি সর্বদা ক্যাশ করা থাকে, যা কনফিগারেশন পরিবর্তনের পরে দ্রুত স্টেট পুনরুদ্ধারের জন্য সহায়ক।

class NewsViewModel(...) : ViewModel() {

    val uiState: StateFlow<NewsUiState> = 
}

UiState এর একটি স্ট্রিম তৈরি করার একটি প্রচলিত উপায় হলো ViewModel থেকে একটি সহায়ক পরিবর্তনযোগ্য স্ট্রিমকে অপরিবর্তনযোগ্য স্ট্রিম হিসেবে প্রকাশ করা—উদাহরণস্বরূপ, একটি MutableStateFlow<UiState> কে StateFlow<UiState> হিসেবে প্রকাশ করা।

class NewsViewModel(...) : ViewModel() {

    private val _uiState = MutableStateFlow(NewsUiState())
    val uiState: StateFlow<NewsUiState> = _uiState.asStateFlow()

    ...

}

এরপর ViewModel এমন মেথডগুলো প্রকাশ করতে পারে যা অভ্যন্তরীণভাবে স্টেট পরিবর্তন করে এবং UI-এর ব্যবহারের জন্য আপডেটগুলো প্রকাশ করে। উদাহরণস্বরূপ, এমন একটি পরিস্থিতি বিবেচনা করুন যেখানে একটি অ্যাসিঙ্ক্রোনাস অ্যাকশন সম্পাদন করার প্রয়োজন; সেক্ষেত্রে viewModelScope ব্যবহার করে একটি কো-রুটিন চালু করা যেতে পারে এবং কাজটি সম্পন্ন হলে পরিবর্তনযোগ্য স্টেটটি আপডেট করা যেতে পারে।

class NewsViewModel(
    private val repository: NewsRepository,
    ...
) : ViewModel() {

    private val _uiState = MutableStateFlow(NewsUiState())
    val uiState: StateFlow<NewsUiState> = _uiState.asStateFlow()

    private var fetchJob: Job? = null

    fun fetchArticles(category: String) {
        fetchJob?.cancel()
        fetchJob = viewModelScope.launch {
            try {
                val newsItems = repository.newsItemsForCategory(category)
                _uiState.update {
                    it.copy(newsItems = newsItems)
                }
            } catch (ioe: IOException) {
                // Handle the error and notify the UI when appropriate.
                _uiState.update {
                    val messages = getMessagesFromThrowable(ioe)
                    it.copy(userMessages = messages)
                }
            }
        }
    }
}

UI অবস্থা গ্রহণ করুন

UI-তে অবজার্ভেবল ডেটা হোল্ডার ব্যবহার করার সময়, UI-এর লাইফসাইকেল অবশ্যই বিবেচনায় রাখবেন। এটি গুরুত্বপূর্ণ কারণ যখন ভিউটি ব্যবহারকারীকে দেখানো হচ্ছে না, তখন UI-এর নিজের স্টেট অবজার্ভ করা উচিত নয়। এই বিষয়ে আরও জানতে, এই ব্লগ পোস্টটি দেখুন। LiveData ব্যবহার করার সময়, LifecycleOwner স্বয়ংক্রিয়ভাবে লাইফসাইকেল সংক্রান্ত বিষয়গুলোর যত্ন নেয়। ফ্লো ব্যবহার করার ক্ষেত্রে, উপযুক্ত কো-রুটিন স্কোপ এবং repeatOnLifecycle API ব্যবহার করে এটি পরিচালনা করাই সবচেয়ে ভালো।

class NewsActivity : AppCompatActivity() {

    private val viewModel: NewsViewModel by viewModels()

    override fun onCreate(savedInstanceState: Bundle?) {
        ...

        lifecycleScope.launch {
            repeatOnLifecycle(Lifecycle.State.STARTED) {
                viewModel.uiState.collect {
                    // Update UI elements
                }
            }
        }
    }
}

চলমান কার্যক্রম দেখান

একটি UiState ক্লাসে লোডিং অবস্থা প্রকাশ করার একটি সহজ উপায় হলো একটি বুলিয়ান ফিল্ড ব্যবহার করা:

data class NewsUiState(
    val isFetchingArticles: Boolean = false,
    ...
)

এই ফ্ল্যাগের মান UI-তে প্রোগ্রেস বারের উপস্থিতি বা অনুপস্থিতি নির্দেশ করে।

class NewsActivity : AppCompatActivity() {

    private val viewModel: NewsViewModel by viewModels()

    override fun onCreate(savedInstanceState: Bundle?) {
        ...

        lifecycleScope.launch {
            repeatOnLifecycle(Lifecycle.State.STARTED) {
                // Bind the visibility of the progressBar to the state
                // of isFetchingArticles.
                viewModel.uiState
                    .map { it.isFetchingArticles }
                    .distinctUntilChanged()
                    .collect { progressBar.isVisible = it }
            }
        }
    }
}

অ্যানিমেশন

সাবলীল এবং মসৃণ টপ-লেভেল নেভিগেশন ট্রানজিশন নিশ্চিত করার জন্য, অ্যানিমেশন শুরু করার আগে আপনি দ্বিতীয় স্ক্রিনের ডেটা লোড হওয়া পর্যন্ত অপেক্ষা করতে চাইতে পারেন। অ্যান্ড্রয়েড ভিউ ফ্রেমওয়ার্ক postponeEnterTransition() এবং startPostponedEnterTransition() API-এর মাধ্যমে ফ্র্যাগমেন্ট ডেস্টিনেশনগুলোর মধ্যে ট্রানজিশন বিলম্বিত করার জন্য হুক প্রদান করে। এই API-গুলো নিশ্চিত করে যে, UI সেই স্ক্রিনে ট্রানজিশন অ্যানিমেট করার আগে দ্বিতীয় স্ক্রিনের UI এলিমেন্টগুলো (সাধারণত নেটওয়ার্ক থেকে আনা একটি ছবি) প্রদর্শনের জন্য প্রস্তুত আছে।

{% হুবহু %} {% endverbatim %} {% হুবহু %} {% endverbatim %}