কম্পিউটার

প্রো-এর মতো অ্যান্ড্রয়েডে মডেল-ভিউ-ভিউ মডেল কীভাবে ব্যবহার করবেন

এই নিবন্ধে আমার লক্ষ্য হল ব্যাখ্যা করা কেন মডেল-ভিউ-ভিউমডেল স্থাপত্য প্যাটার্ন কিছু পরিস্থিতিতে একটি GUI আর্কিটেকচারের উপস্থাপনা যুক্তির ক্ষেত্রে উদ্বেগের একটি খুব বিশ্রী বিচ্ছেদ উপস্থাপন করে।

আমরা MVVM এর দুটি রূপ অন্বেষণ করব (এখানে না আছে এটি করার শুধুমাত্র একটি উপায়), এবং প্রকল্পের প্রয়োজনীয়তার উপর ভিত্তি করে আপনি কেন একটি বৈকল্পিককে অন্যটির চেয়ে পছন্দ করতে পারেন।

MVVM বনাম MVP/MVC?

সম্ভবত আমার লাইভ রবিবারের প্রশ্নোত্তর সেশনের সময় আমাকে সবচেয়ে সাধারণ প্রশ্ন করা হয়, তা হল:

MVVM বনাম MVP/MVC?

যখনই আমাকে এই প্রশ্নটি জিজ্ঞাসা করা হয়, আমি দ্রুত এই ধারণাটি জোর দিই যে কোনও একক GUI আর্কিটেকচার সমস্ত পরিস্থিতিতে দুর্দান্ত কাজ করে না।

কেন, আপনি জিজ্ঞাসা করতে পারেন? একটি প্রদত্ত অ্যাপ্লিকেশনের জন্য সর্বোত্তম স্থাপত্য (বা অন্তত একটি ভাল পছন্দ) হাতের প্রয়োজনীয়তার উপর দৃঢ়ভাবে নির্ভর করে।

আসুন সংক্ষিপ্তভাবে এই শব্দের প্রয়োজনীয়তা সম্পর্কে চিন্তা করি আসলে মানে:

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

আমি একটি নিবন্ধ লিখতে চাই যেখানে আমি উপরে তালিকাভুক্ত প্রয়োজনীয়তা এবং উদ্বেগের সাথে MVVM কীভাবে কাজ করে তা বিস্তারিতভাবে আলোচনা করি। দুর্ভাগ্যবশত, আপনাদের মধ্যে কেউ কেউ এই ভেবে বিভ্রান্ত হয়েছেন যে MVVM করার একমাত্র উপায় আছে।

পরিবর্তে, আমি MVVM-এর সাধারণ ধারণার দুটি ভিন্ন পন্থা নিয়ে আলোচনা করব যা খুবই স্বতন্ত্র সুবিধা এবং অসুবিধাগুলি উপস্থাপন করে। তবে প্রথমে সাধারণ ধারণা দিয়ে শুরু করা যাক।

আপনি আপনার ভিউ ক্লাসের উল্লেখ করবেন না

আমার বন্ধুদের জন্য যারা পুরানো ইংরেজি পড়তে পারে না:আপনি হয়ত ভিউ ক্লাস উল্লেখ নাও করতে পারেন "

ViewModel নামটি ব্যবহার করা ছাড়াও (যা নিজেই বিভ্রান্তিকর যদি ক্লাসটি যুক্তি পূর্ণ হয় ), MVVM আর্কিটেকচারের একটি লৌহ-পরিহিত নিয়ম হল যে আপনি কখনই ভিউমডেল থেকে একটি ভিউ উল্লেখ করতে পারবেন না।

এখন, বিভ্রান্তির প্রথম ক্ষেত্রটি এই "রেফারেন্স" শব্দ থেকে উদ্ভূত হতে পারে, যা আমি বিভিন্ন স্তরের জারগন ব্যবহার করে পুনরায় বর্ণনা করব:

  • আপনার ViewModels কোনো ভিউতে কোনো রেফারেন্স (সদস্য ভেরিয়েবল, বৈশিষ্ট্য, পরিবর্তনযোগ্য/পরিবর্তনযোগ্য ক্ষেত্র) নাও থাকতে পারে
  • আপনার ViewModels কোনো ভিউয়ের উপর নির্ভর নাও করতে পারে
  • আপনার ভিউমডেল আপনার ভিউয়ের সাথে সরাসরি কথা বলতে পারে না

এখন, অ্যান্ড্রয়েড প্ল্যাটফর্মে, এই নিয়মের কারণটি কেবল এই নয় যে এটি ভাঙা খারাপ কারণ কেউ সফ্টওয়্যার আর্কিটেকচার সম্পর্কে জানেন বলে মনে হচ্ছে এটি খারাপ।

আর্কিটেকচার কম্পোনেন্টস থেকে ভিউমডেল ক্লাস ব্যবহার করার সময় (যা এর উদাহরণ স্থির করার জন্য ডিজাইন করা হয়েছে ফ্র্যাগমেন্ট/অ্যাক্টিভিটি লাইফসাইকেলের চেয়ে দীর্ঘ যখন উপযুক্ত ), একটি ভিউ উল্লেখ করে গুরুতর মেমরি লিকস চাইছে .

কেন MVVM সাধারণভাবে এই ধরনের রেফারেন্সের অনুমতি দেয় না, লক্ষ্য হল অনুমানিকভাবে ভিউ এবং ভিউমডেল উভয়কেই পরীক্ষা করা এবং লেখা সহজ করতে।

অন্যরা এটাও উল্লেখ করতে পারে যে এটি ViewModels-এর পুনঃব্যবহারযোগ্যতাকে উৎসাহিত করে, কিন্তু এটি ঠিক যেখানে জিনিসগুলি এই প্যাটার্নের সাথে ভেঙে যায় .

আমরা কোডটি দেখার আগে, দয়া করে মনে রাখবেন যে আমি ব্যক্তিগতভাবে LiveData ব্যবহার করি না৷ আমার নিজের প্রোডাকশন কোডে। আমি আজকাল আমার নিজের প্রকাশক-সাবস্ক্রাইবার প্যাটার্ন লিখতে পছন্দ করি, কিন্তু আমি নীচে যা বলছি তা যেকোন লাইব্রেরিতে প্রযোজ্য যা ভিউমডেল থেকে ভিউতে পাবসাব/অবজারভার প্যাটার্ন লিঙ্কের জন্য অনুমতি দেয়।

এই নিবন্ধটি একটি ভিডিও টিউটোরিয়াল সহ এখানে একই ধারণাগুলির অনেকগুলি কভার করে:

ViewLogic + ViewModel বা View + ViewModelController?

আমি যখন পূর্ববর্তী বিভাগে "ব্রেক ডাউন" বলেছিলাম, তখন আমি বলতে চাচ্ছি না যে প্যাটার্নটি আক্ষরিকভাবে ভেঙে গেছে। আমি বলতে চাচ্ছি যে এটি (অন্তত) দুটি ভিন্ন পদ্ধতিতে ভেঙে যায় যার খুব স্বতন্ত্র উপস্থিতি, সুবিধা এবং ফলাফল রয়েছে।

আসুন আমরা এই দুটি পন্থা বিবেচনা করি, এবং আপনি কখন একটিকে অন্যটির চেয়ে পছন্দ করতে পারেন।

প্রো-এর মতো অ্যান্ড্রয়েডে মডেল-ভিউ-ভিউ মডেল কীভাবে ব্যবহার করবেন
বোরোমির ব্যাখ্যা করে যে MVVM কোন জাদুর কাঠি নয় যা আপনার অ্যাপ্লিকেশনের উপস্থাপনার যুক্তিকে অদৃশ্য করে দেয়।

প্রথম পদ্ধতি:পুনঃব্যবহারযোগ্য ভিউ মডেলকে অগ্রাধিকার দিন

যতদূর আমি বলতে পারি, বেশিরভাগ লোকেরা যারা MVVM প্রয়োগ করে তারা ভিউমডেলগুলির পুনঃব্যবহারযোগ্যতাকে উন্নীত করার লক্ষ্য তৈরি করে, যাতে সেগুলি n এর জন্য পুনরায় ব্যবহার করা যেতে পারে। বিভিন্ন দর্শনের সংখ্যা (অনেক থেকে এক অনুপাত)।

সহজ কথায়, দুটি উপায়ে আপনি এই পুনঃব্যবহারযোগ্যতা অর্জন করতে পারেন:

  • একটি নির্দিষ্ট ভিউ উল্লেখ না করে। আশা করি এই মুহুর্তে এটি আপনার কাছে খবর নয়৷
  • জানার মাধ্যমে UI -এর বিস্তারিত সম্পর্কে যতটা সম্ভব কম সাধারণভাবে

দ্বিতীয় পয়েন্টটি অস্পষ্ট বা পাল্টা স্বজ্ঞাত মনে হতে পারে (যেটি এটি উল্লেখ করে না এমন কিছু সম্পর্কে এটি কীভাবে কিছু জানতে পারে?), তাই আমি মনে করি এটি কিছু কোড দেখার সময়:

class NoteViewModel(val repo: NoteRepo): ViewModel(){
    //Note: you may also publish data to the View via Databinding, RxJava Observables, and other approaches. Although I do not like to use LiveData in back end classes, it works great with Android front end with AAC
    val noteState: MutableLiveData<Note>()
    //...
    fun handleEvent(event: NoteEvent) {
        when (event) {
            is NoteEvent.OnStart -> getNote(event.noteId)
            //...
        }
    }
    private fun getNote(noteId: String){
        noteState.value = repo.getNote(noteId)
    }
}

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

data class Note(val creationDate:String,
                val contents:String,
                val imageUrl: String,
                val creator: User?)

এই বিশেষ পদ্ধতির সাথে, ভিউমডেলটি শুধুমাত্র একটি নির্দিষ্ট ভিউ থেকে নয়, বিশদ বিবরণ এবং এক্সটেনশনের মাধ্যমে, প্রেজেন্টেশন লজিক থেকে ভাল এবং সত্যিকারের ডিকপল করা হয়েছে। কোনো বিশেষ দৃষ্টিভঙ্গির।

আমি যা বলছি তা যদি এখনও অস্পষ্ট মনে হয়, আমি প্রতিশ্রুতি দিচ্ছি যে আমি অন্য পদ্ধতির বর্ণনা করার পরে এটি পরিষ্কার হয়ে যাবে৷

যদিও আমার আগের শিরোনাম, “ViewLogic + ViewModel… ” এর অর্থ ব্যবহার করা বা গুরুত্ব সহকারে নেওয়ার জন্য নয়, আমি বলতে চাচ্ছি যে খুব ডিকপলড এবং পুনরায় ব্যবহারযোগ্য ViewModels থাকার মাধ্যমে, আমরা এখন স্ক্রিনে এই নোট অবজেক্টটিকে কীভাবে রেন্ডার/বাইন্ড করা যায় তা বের করার কাজটি করতে ভিউ-এর উপর নির্ভর করছি।

আমাদের মধ্যে কেউ কেউ লজিক দিয়ে ভিউ ক্লাস পূরণ করা পছন্দ করি না।

এখানেই জিনিসগুলি খুব কাদা হয়ে যায় এবং প্রকল্পের প্রয়োজনীয়তা এর উপর নির্ভরশীল হয়৷ . আমি বলছি না যে ভিউ ক্লাসগুলিকে যুক্তি দিয়ে পূরণ করুন যেমন…:

private fun observeViewModel() {
    viewModel.notes.observe(
        viewLifecycleOwner,
        Observer { notes: List<Note> ->
            if (notes.isEmpty()) showEmptyState()
            else showNoteList(notes)
        }
    )
   //..
}

…হয় সর্বদা একটি খারাপ জিনিস, তবে যে ক্লাসগুলি প্ল্যাটফর্মের সাথে শক্তভাবে সংযুক্ত থাকে (যেমন টুকরোগুলি) সেগুলি পরীক্ষা করা কঠিন এবং সেগুলির মধ্যে যুক্তি সহ ক্লাসগুলি পরীক্ষা করার জন্য সবচেয়ে গুরুত্বপূর্ণ ক্লাস!

এক কথায়, আমি যে কোনো ভালো স্থাপত্যের সুবর্ণ নীতি বলে মনে করি তা প্রয়োগ করা ব্যর্থতা:উদ্বেগের বিচ্ছেদ

আমার ব্যক্তিগত মতামত হল উদ্বেগের বিচ্ছেদকে খুব উচ্চ মাত্রায় প্রয়োগ করা মূল্যবান। কিন্তু কোন ভুল করবেন না যে প্রচুর পরিমাণে নগদ গরুর আবেদন এমন লোকেদের দ্বারা লেখা হয়েছে যারা এর অর্থ কী তা সম্পর্কে অস্পষ্ট ধারণা নেই।

যাই হোক না কেন, এর নিজস্ব পার্শ্বপ্রতিক্রিয়া থাকার সময় আমরা পরবর্তী আলোচনা করব পদ্ধতি , আবার ভিউ থেকে উপস্থাপনার যুক্তি সরিয়ে দেয়।

ওয়েল, যাইহোক এর বেশিরভাগই।

সেকেন্ড অ্যাপ্রোচ:নম্র ভিউ, কন্ট্রোল-ফ্রিক ভিউমডেল

কখনও কখনও আপনার ভিউগুলির উপর সূক্ষ্ম নিয়ন্ত্রণ না থাকা (যা ভিউমডেলের পুনঃব্যবহারযোগ্যতাকে অগ্রাধিকার দেওয়ার ফল), আসলে একধরনের খারাপ৷

পূর্ববর্তী পদ্ধতিটি নির্বিচারে প্রয়োগ করার বিষয়ে আমাকে আরও কম উত্সাহী করতে, আমি দেখতে পাই যে আমি প্রায়ই করবেন না একটি ভিউমডেল পুনরায় ব্যবহার করতে হবে৷ .

আড়ম্বরপূর্ণভাবে, "অত্যধিক বিমূর্ততা" হল MVVM এর উপর MVP-এর একটি সাধারণ সমালোচনা৷

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

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

মার্টিন ফাউলারের নামকরণের নিয়মে, এটি প্যাসিভ ভিউ/স্ক্রিন নামে পরিচিত। এই পদ্ধতির জন্য একটি আরও সাধারণভাবে প্রযোজ্য নাম হল নম্র অবজেক্ট প্যাটার্ন .

এটি অর্জন করার জন্য, আপনাকে অবশ্যই আপনার ভিউমডেলের একটি পর্যবেক্ষণযোগ্য ক্ষেত্র থাকতে হবে (যদিও আপনি সেটি অর্জন করেন – ডেটা বাইন্ডিং, Rx, LiveData, যাই হোক না কেন) ভিউতে উপস্থিত প্রতিটি নিয়ন্ত্রণ বা উইজেটের জন্য:

class UserViewModel(
    val repo: IUserRepository,
){

    //The actual data model is kept private to avoid unwanted tampering
    private val userState = MutableLiveData<User>()

    //Control Logic
    internal val authAttemptState = MutableLiveData<Unit>()
    internal val startAnimation = MutableLiveData<Unit>()

    //UI Binding
    internal val signInStatusText = MutableLiveData<String>()
    internal val authButtonText = MutableLiveData<String>()
    internal val satelliteDrawable = MutableLiveData<String>()

    private fun showErrorState() {
        signInStatusText.value = LOGIN_ERROR
        authButtonText.value = SIGN_IN
        satelliteDrawable.value = ANTENNA_EMPTY
    }
    //...
}

পরবর্তীকালে, ভিউকে এখনও ভিউমডেল পর্যন্ত নিজেকে সংযুক্ত করতে হবে, তবে এটি করার জন্য প্রয়োজনীয় ফাংশনগুলি লিখতে তুচ্ছভাবে সহজ হয়ে যায়:

class LoginView : Fragment() {

    private lateinit var viewModel: UserViewModel
    //...
    
    //Create and bind to ViewModel
    override fun onStart() {
        super.onStart()
        viewModel = ViewModelProviders.of(
        //...   
        ).get(UserViewModel::class.java)

        //start background anim
        (root_fragment_login.background as AnimationDrawable).startWithFade()

        setUpClickListeners()
        observeViewModel()

        viewModel.handleEvent(LoginEvent.OnStart)
    }

    private fun setUpClickListeners() {
      //...
    }

    private fun observeViewModel() {
        viewModel.signInStatusText.observe(
            viewLifecycleOwner,
            Observer {
                //"it" is the value of the MutableLiveData object, which is inferred to be a String automatically
                lbl_login_status_display.text = it
            }
        )

        viewModel.authButtonText.observe(
            viewLifecycleOwner,
            Observer {
                btn_auth_attempt.text = it
            }
        )

        viewModel.startAnimation.observe(
            viewLifecycleOwner,
            Observer {
                imv_antenna_animation.setImageResource(
                    resources.getIdentifier(ANTENNA_LOOP, "drawable", activity?.packageName)
                )
                (imv_antenna_animation.drawable as AnimationDrawable).start()
            }
        )

        viewModel.authAttemptState.observe(
            viewLifecycleOwner,
            Observer { startSignInFlow() }
        )

        viewModel.satelliteDrawable.observe(
            viewLifecycleOwner,
            Observer {
                imv_antenna_animation.setImageResource(
                    resources.getIdentifier(it, "drawable", activity?.packageName)
                )
            }
        )
    }

আপনি এখানে এই উদাহরণের জন্য সম্পূর্ণ কোড খুঁজে পেতে পারেন।

আপনি সম্ভবত লক্ষ্য করেছেন, আমরা সম্ভবত এই ViewModel অন্য কোথাও পুনরায় ব্যবহার করতে যাচ্ছি না . এছাড়াও, আমাদের ভিউ যথেষ্ট নম্র হয়ে উঠেছে (কোড কভারেজের জন্য আপনার মান এবং পছন্দগুলির উপর নির্ভর করে), এবং লেখার জন্য খুব সহজ৷

কখনও কখনও আপনি এমন পরিস্থিতিতে চলে যাবেন যেখানে আপনাকে অবশ্যই উপস্থাপনা যুক্তির বিতরণের মধ্যে কিছু অর্ধ-পরিমাপ খুঁজে পেতে হবে ভিউ এবং ViewModels এর মধ্যে, যা এই পদ্ধতির কোনটিই কঠোরভাবে অনুসরণ করে না।

আমি একটি পদ্ধতির উপর অন্য পদ্ধতির পক্ষে সমর্থন করছি না, বরং প্রয়োজনের উপর ভিত্তি করে আপনার পদ্ধতিতে নমনীয় হতে আপনাকে উত্সাহিত করছি৷

পছন্দ এবং প্রয়োজনীয়তার উপর ভিত্তি করে আপনার স্থাপত্য চয়ন করুন

এই নিবন্ধটির উদ্দেশ্য ছিল দুটি ভিন্ন পদ্ধতির দিকে নজর দেওয়া যা একজন ডেভেলপার অ্যান্ড্রয়েড প্ল্যাটফর্মে একটি MVVM শৈলীর GUI আর্কিটেকচার নির্মাণের ক্ষেত্রে নিতে পারে (কিছু অন্য প্ল্যাটফর্মে নিয়ে যাওয়া সহ)।

প্রকৃতপক্ষে, আমরা এই দুটি পদ্ধতির মধ্যেও ছোট পার্থক্য সম্পর্কে আরও নির্দিষ্ট করতে পারি।

  • ভিউ কি প্রতিটি স্বতন্ত্র উইজেট/নিয়ন্ত্রণের জন্য একটি ক্ষেত্র পর্যবেক্ষণ করবে, অথবা এটি একটি ক্ষেত্র পর্যবেক্ষণ করবে যা একটি একক মডেল প্রকাশ করে প্রতিবার পুরো ভিউ নতুন করে রেন্ডার করতে?
  • হয়তো আমরা আমাদের ভিউমডেলগুলিকে এক-টু-ওয়ান করা এড়াতে পারি, আমাদের দৃষ্টিভঙ্গিগুলিকে নম্র অবজেক্ট হিসাবে রাখার সময়, শুধুমাত্র একটি উপস্থাপক বা কন্ট্রোলারের মতো কিছু যোগ করে মিশ্রণে?

কথা বলা সস্তা, এবং আমি দৃঢ়ভাবে আপনাকে এই জিনিসগুলি কোডে চেষ্টা করে শেখার পরামর্শ দিচ্ছি যাতে আপনাকে কী করতে হবে তা বলার জন্য আমার মতো লোকেদের উপর নির্ভর করতে হবে না।

পরিশেষে, আমি মনে করি যে দুটি উপাদান যা একটি দুর্দান্ত স্থাপত্য তৈরি করে তা নিম্নলিখিত বিবেচনায় আসে:

প্রথমত, আপনি যেটিকে পছন্দ করেন খুঁজে না পাওয়া পর্যন্ত বিভিন্ন পদ্ধতির সাথে খেলুন . প্রতিটি স্টাইলে আসলে একটি অ্যাপ্লিকেশন তৈরি করে (এটি সহজ হতে পারে) এবং সঠিক মনে হয় দেখে এটি সর্বোত্তম করা হয় .

দ্বিতীয়ত, বুঝুন যে পছন্দগুলি একপাশে, বিভিন্ন শৈলী বিভিন্ন ঘাটতির বিনিময়ে বিভিন্ন সুবিধার উপর জোর দেয়। অবশেষে, আপনি অন্ধ বিশ্বাসের পরিবর্তে প্রকল্পের প্রয়োজনীয়তা সম্পর্কে আপনার বোঝার উপর ভিত্তি করে ভাল পছন্দ বেছে নিতে সক্ষম হবেন। .

সফ্টওয়্যার আর্কিটেকচার সম্পর্কে আরও জানুন:

সামাজিক

https://www.instagram.com/rkay301/
https://www.facebook.com/wiseassblog/
https://twitter.com/wiseass301
https://wiseassblog.com/


  1. কিভাবে Android এ NavigationView ব্যবহার করবেন?

  2. প্রো-এর মতো অ্যান্ড্রয়েডে কীভাবে স্ক্রিনশট এবং চিত্রগুলি টীকা করা যায়

  3. উইন্ডোজ 11/10 টাস্ক ম্যানেজার আইটি প্রো এর মতো কীভাবে ব্যবহার করবেন

  4. একজন পেশাদারের মতো Google চিত্র অনুসন্ধান কীভাবে ব্যবহার করবেন