কম্পিউটার

আপনার অ্যান্ড্রয়েড অ্যাপস আর্কিটেকচারকে কীভাবে সহজ করবেন:কোড নমুনা সহ একটি বিশদ নির্দেশিকা

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

এটি খারাপ - তারা তাদের কোড মোকাবেলা করতে সক্ষম হবে না। এবং পরবর্তী বিকাশকারী যাকে প্রকল্পটি বজায় রাখতে বা এটি সংশোধন করতে হবে সে পাগল হয়ে যেতে পারে। স্ক্র্যাচ থেকে এই জাতীয় প্রকল্পগুলি পুনর্নির্মাণ করা ভাল, যেহেতু রক্ষণাবেক্ষণ একটি জটিল প্রক্রিয়া হয়ে ওঠে।

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

এই মুহুর্তে, অ্যান্ড্রয়েড বিকাশকারীদের জন্য 16টি ভিন্ন আর্কিটেকচার রয়েছে, গুগলকে ধন্যবাদ:

  • 6 স্থিতিশীল নমুনা (জাভা);
  • 2 স্থিতিশীল নমুনা (কোটলিন):
  • 4টি বাহ্যিক নমুনা;
  • 3টি অবলুপ্ত নমুনা;
  • 1 নমুনা চলছে।

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

যাইহোক, এই সমস্ত আর্কিটেকচারের একটি সাধারণ স্থাপত্যের ভিত্তি রয়েছে যা নেটওয়ার্ক, ডাটাবেস, নির্ভরতা এবং কলব্যাক প্রক্রিয়াকরণের সাথে কাজ করার যুক্তিকে প্রায় সমানভাবে ভাগ করে।

প্রক্রিয়া চলাকালীন ব্যবহৃত টুলস

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

আপনার অ্যান্ড্রয়েড অ্যাপস আর্কিটেকচারকে কীভাবে সহজ করবেন:কোড নমুনা সহ একটি বিশদ নির্দেশিকা

এখানে আমি যে প্রযুক্তি ব্যবহার করেছি তার একটি সারসংক্ষেপ:

  • কোটলিন AndroidX-এর সাথে অ্যাপটি বিকাশ করতে লাইব্রেরি
  • রুম SQLite একটি ডাটাবেস হিসাবে
  • স্টেথো বেসে ডাটা ব্রাউজ করতে
  • রেট্রোফিট2 সার্ভারের অনুরোধগুলি লগ করতে এবং সার্ভারের প্রতিক্রিয়া পেতে সাহায্য করতে RxJava2 এর সাথে।
  • গ্লাইড ছবি প্রক্রিয়া করতে
  • Android আর্কিটেকচার উপাদান (LiveData, ViewModel, Room) এবং ReactiveX (RxJava2, RxKotlin এবং RxAndroid) নির্ভরতা, গতিশীল ডেটা পরিবর্তন এবং অ্যাসিঙ্ক্রোনি প্রক্রিয়াকরণের জন্য।

তাই এই মোবাইল অ্যাপ প্রযুক্তি স্ট্যাক আমি আমার প্রকল্পের জন্য ব্যবহার করেছি।

আসুন শুরু করা যাক

প্রথম ধাপ

AndroidX সংযোগ করুন . gradle.properties-এ অ্যাপ স্তরে, নিম্নলিখিতটি লিখুন:

android.enableJetifier=true
android.useAndroidX=true

এখন build.gradle -এ নির্ভরতা প্রতিস্থাপন করা প্রয়োজন অ্যান্ড্রয়েড থেকে অ্যান্ড্রয়েডএক্স পর্যন্ত অ্যাপ মডিউল স্তরে। আপনাকে ext, -এ সমস্ত নির্ভরতা বের করতে হবে আপনি build.gradle -এ কোটলিনের আউট-অফ-দ্য-বক্স সংস্করণের উদাহরণে দেখতে পাচ্ছেন অ্যাপ স্তরে। এবং তারপর আমি সেখানে গ্রেডল সংস্করণ যোগ করি:

buildscript {
    ext.kotlin_version = '1.3.0'
    ext.gradle_version = '3.2.1'

    repositories {
        google()
        jcenter()
        maven { url 'https://jitpack.io' }
        mavenCentral()
    }
    dependencies {
        classpath "com.android.tools.build:gradle:$gradle_version"
        classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"

        // NOTE: Do not place your application dependencies here; they belong
        // in the individual module build.gradle files
    }
}

অন্যান্য সমস্ত নির্ভরতার জন্য, আমি এটির এক্সট তৈরি করব ফাইল, যেখানে আমি SDK সংস্করণ, সংস্করণ ভাগ করা এবং নির্ভরতা massifs তৈরি সহ সম্পূর্ণরূপে সমস্ত নির্ভরতা যোগ করি যা build.gradle -এ আরও প্রয়োগ করা হবে অ্যাপ স্তরে। এটি নিচের মত দেখাবে:

ext {
    compileSdkVersion = 28
    minSdkVersion = 22
    buildToolsVersion = '28.0.3'
    targetSdkVersion = 28

    appcompatVersion = '1.0.2'
    supportVersion = '1.0.0'
    supportLifecycleExtensionsVersion = '2.0.0'
    constraintlayoutVersion = '1.1.3'
    multiDexVersion = "2.0.0"

    testJunitVersion = '4.12'
    testRunnerVersion = '1.1.1'
    testEspressoCoreVersion = '3.1.1'

    testDependencies = [
            junit       : "junit:junit:$testJunitVersion",
            runner      : "androidx.test:runner:$testRunnerVersion",
            espressoCore: "androidx.test.espresso:espresso-core:$testEspressoCoreVersion"
    ]

    supportDependencies = [
            kotlin            : "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version",
            appCompat         : "androidx.appcompat:appcompat:$appcompatVersion",
            recyclerView      : "androidx.recyclerview:recyclerview:$supportVersion",
            design            : "com.google.android.material:material:$supportVersion",
            lifecycleExtension: "androidx.lifecycle:lifecycle-extensions:$supportLifecycleExtensionsVersion",
            constraintlayout  : "androidx.constraintlayout:constraintlayout:$constraintlayoutVersion",
            multiDex          : "androidx.multidex:multidex:$multiDexVersion"
    ]
}

সংস্করণ এবং massif নামগুলি এলোমেলোভাবে প্রয়োগ করা হয়। এর পরে, আমরা build.gradle -এ নির্ভরতা প্রয়োগ করব অ্যাপ স্তরে নিম্নরূপ:

apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
apply plugin: 'kotlin-android-extensions'
apply plugin: 'kotlin-kapt'

android {
    compileSdkVersion rootProject.ext.compileSdkVersion as Integer
    buildToolsVersion rootProject.ext.buildToolsVersion as String
dependencies {
    implementation fileTree(dir: 'libs', include: ['*.jar'])

    //Test
    testImplementation testDependencies.junit
    androidTestImplementation testDependencies.runner
    androidTestImplementation testDependencies.espressoCore

    //Support
    implementation supportDependencies.kotlin
    implementation supportDependencies.appCompat
    implementation supportDependencies.recyclerView
    implementation supportDependencies.design
    implementation supportDependencies.lifecycleExtension
    implementation supportDependencies.constraintlayout
    implementation supportDependencies.multiDex

multiDexEnabled true উল্লেখ করতে ভুলবেন না ডিফল্ট কনফিগারেশনে। বেশিরভাগ ক্ষেত্রে, আপনি দ্রুত ব্যবহৃত পদ্ধতির সংখ্যার সীমাতে পৌঁছে যাবেন।

একইভাবে, আপনাকে অ্যাপের সমস্ত নির্ভরতা ঘোষণা করতে হবে। ইন্টারনেটের সাথে আমাদের অ্যাপ সংযোগ করার অনুমতি যোগ করা যাক:

 <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
    <uses-permission android:name="android.permission.INTERNET" />

যদি ম্যানিফেস্টে কোনো নাম যোগ করা না থাকে, তাহলে আপনাকে Stetho থেকে এটি করা উচিত নামহীন অ্যাপটি দেখতে পাবে না এবং আপনি ডাটাবেসটি দেখতে সক্ষম হবেন না।

মৌলিক উপাদান নির্মাণ

এটি লক্ষণীয় যে এমভিভিএম (মডেল-ভিউ-ভিউমডেল) প্যাটার্নটি এই স্থাপত্য নির্মাণের ভিত্তি হিসাবে ব্যবহৃত হয়েছিল।

উন্নয়ন শুরু করা যাক। আপনাকে প্রথমে যা করতে হবে তা হল একটি ক্লাস তৈরি করা যা অ্যাপ্লিকেশন() উত্তরাধিকারী হবে। এই ক্লাসে, আমরা এটির আরও ব্যবহারের জন্য অ্যাপ প্রসঙ্গে অ্যাক্সেস দেব।

@SuppressWarnings("all")
class App : Application() {

    companion object {
        lateinit var instance: App
            private set
    }

    override fun onCreate() {
        super.onCreate()
        instance = this
        Stetho.initializeWithDefaults(this)
        DatabaseCreator.createDatabase(this)
    }
}

দ্বিতীয় ধাপ হল ভিউমডেল থেকে শুরু করে মৌলিক অ্যাপ উপাদান তৈরি করা, যা আমি প্রতিটি কার্যকলাপ বা খণ্ডের জন্য ব্যবহার করব।

abstract class BaseViewModel constructor(app: Application) : AndroidViewModel(app) {

    override fun onCleared() {
        super.onCleared()
    }
}

এই অ্যাপটির কোনো জটিল কার্যকারিতা নেই। কিন্তু মৌলিক ViewModel-এ আমরা 3টি প্রধান LiveData রাখব :

  • ত্রুটি প্রক্রিয়াকরণ
  • প্রোগ্রেস বারের সাথে লোডিং প্রসেসিং প্রদর্শিত হচ্ছে
  • এবং, যেহেতু আমার কাছে তালিকা সহ একটি অ্যাপ রয়েছে, তাই অ্যাডাপ্টারে রসিদ এবং ডেটা উপলব্ধতা একটি স্থানধারক হিসাবে প্রক্রিয়া করা হচ্ছে যা তাদের অনুপস্থিতিতে প্রদর্শিত হয়৷
val errorLiveData = MediatorLiveData<String>()
    val isLoadingLiveData = MediatorLiveData<Boolean>()
    val isEmptyDataPlaceholderLiveData = MediatorLiveData<Boolean>()

লাইভডেটাতে ফাংশন বাস্তবায়নের ফলাফল স্থানান্তর করতে আমি ভোক্তা ব্যবহার করব .

অ্যাপের যেকোনো স্থানে ত্রুটিগুলি প্রক্রিয়া করতে, আপনাকে একটি গ্রাহক তৈরি করতে হবে যেটি Throwable.message স্থানান্তর করবে errorLiveData এর মান .

এছাড়াও, মৌলিক VewModel-এ, আপনাকে একটি পদ্ধতি তৈরি করতে হবে যা তাদের বাস্তবায়নের সময় অগ্রগতি বার প্রদর্শন করার জন্য একটি LiveData তালিকা পাবে।

আমাদের বেসিক ভিউমডেল দেখতে এরকম হবে:

abstract class BaseViewModel constructor(app: Application) : AndroidViewModel(app) {

    val errorLiveData = MediatorLiveData<String>()
    val isLoadingLiveData = MediatorLiveData<Boolean>()
    val isEmptyDataPlaceholderLiveData = MediatorLiveData<Boolean>()

    private var compositeDisposable: CompositeDisposable? = null

    protected open val onErrorConsumer = Consumer<Throwable> {
        errorLiveData.value = it.message
    }

    fun setLoadingLiveData(vararg mutableLiveData: MutableLiveData<*>) {
        mutableLiveData.forEach { liveData ->
            isLoadingLiveData.apply {
                this.removeSource(liveData)
                this.addSource(liveData) { this.value = false }
            }
        }
    }

    override fun onCleared() {
        isLoadingLiveData.value = false
        isEmptyDataPlaceholderLiveData.value = false
        clearSubscription()
        super.onCleared()
    }

    private fun clearSubscription() {
        compositeDisposable?.apply {
            if (!isDisposed) dispose()
            compositeDisposable = null
        }
    }
}


আমাদের অ্যাপে দুটি স্ক্রিনের জন্য কয়েকটি অ্যাক্টিভিটি (সংবাদ তালিকার স্ক্রিন এবং পছন্দের তালিকার স্ক্রীন) তৈরি করার কোনো মানে হয় না। কিন্তু যেহেতু এই নমুনাটি সর্বোত্তম এবং সহজে এক্সটেনসিবল আর্কিটেকচারের বাস্তবায়ন দেখায়, তাই আমি একটি মৌলিক অ্যাপ তৈরি করব।

আমাদের অ্যাপটি 1টি অ্যাক্টিভিটি এবং 2টি ফ্র্যাগমেন্টের উপর তৈরি করা হবে যা আমরা কন্টেইনার অ্যাক্টিভিটিতে ফুলিয়ে দেব। আমাদের কার্যকলাপের XML ফাইলটি নিম্নরূপ হবে:

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="https://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <FrameLayout
        android:id="@+id/flContainer"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />

    <include layout="@layout/include_placeholder"/>

    <include layout="@layout/include_progress_bar" />
</FrameLayout>

যেখানে include_placeholder এবং include_progressbar এই মত দেখাবে:

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="https://schemas.android.com/apk/res/android"
    android:id="@+id/flProgress"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="@color/bg_black_40">

    <ProgressBar
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center"
        android:background="@color/transparent" />
</FrameLayout>
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="https://schemas.android.com/apk/res/android"
    android:id="@+id/flPlaceholder"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="@color/bg_transparent">

    <ImageView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center"
        android:background="@color/transparent"
        android:src="@drawable/ic_business_light_blue_800_24dp" />

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center"
        android:layout_marginTop="40dp"
        android:text="@string/empty_data"
        android:textColor="@color/colorPrimary"
        android:textStyle="bold" />
</FrameLayout>

আমাদের বেসঅ্যাক্টিভিটি এরকম দেখাবে:

abstract class BaseActivity<T : BaseViewModel> : AppCompatActivity(), BackPressedCallback,
        ProgressViewCallback, EmptyDataPlaceholderCallback {

    protected abstract val viewModelClass: Class<T>
    protected abstract val layoutId: Int
    protected abstract val containerId: Int

    protected open val viewModel: T by lazy(LazyThreadSafetyMode.NONE) { ViewModelProviders.of(this).get(viewModelClass) }

    protected abstract fun observeLiveData(viewModel: T)

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(layoutId)
        startObserveLiveData()
    }
    
    private fun startObserveLiveData() {
        observeLiveData(viewModel)
    }
}

ভবিষ্যতের সমস্ত ক্রিয়াকলাপের প্রক্রিয়াগুলিতে সম্ভাব্য ত্রুটিগুলি কীভাবে প্রদর্শন করা যায় তা বাস্তবায়ন করা যাক। আমি সরলতার জন্য সাধারণ টোস্ট আকারে এটি করব।

protected open fun processError(error: String) = Toast.makeText(this, error, Toast.LENGTH_SHORT).show()

এবং প্রদর্শন পদ্ধতিতে এই ত্রুটি পাঠ্য পাঠান:

protected open val errorObserver = Observer<String> { it?.let { processError(it) } }

বেসিক অ্যাক্টিভিটিতে আমি errorLiveData-এর পরিবর্তনগুলি মেনে চলতে শুরু করব মৌলিক ভিউ মডেলে অবস্থিত মান। startObserveLiveData() পদ্ধতিটি নিম্নরূপ রূপান্তরিত হবে:

private fun startObserveLiveData() {
        observeLiveData(viewModel)
        with(viewModel) {
            errorLiveData.observe(this@BaseActivity, errorObserver)
        }
    }

এখন onErrorConsumer ব্যবহার করছেন৷ মৌলিক ViewModel-এর onError হিসেবে প্রসেসর, আপনি বাস্তবায়িত পদ্ধতি ত্রুটি সম্পর্কে বার্তা দেখতে পাবেন।

এমন একটি পদ্ধতি তৈরি করুন যা আপনাকে ব্যাক স্ট্যাকে যোগ করার ক্ষমতা সহ কার্যকলাপের অংশগুলি প্রতিস্থাপন করতে দেয়৷

protected open fun replaceFragment(fragment: Fragment, needToAddToBackStack: Boolean = true) {
        val name = fragment.javaClass.simpleName
        with(supportFragmentManager.beginTransaction()) {
            replace(containerId, fragment, name)
            if (needToAddToBackStack) {
                addToBackStack(name)
            }
            commit()
        }
    }

আসুন প্রয়োজনীয় অ্যাপ স্পটগুলিতে অগ্রগতি এবং স্থানধারক প্রদর্শনের জন্য ইন্টারফেস তৈরি করি।

interface EmptyDataPlaceholderCallback {

    fun onShowPlaceholder()

    fun onHidePlaceholder()
}
interface ProgressViewCallback {

    fun onShowProgress()

    fun onHideProgress()
}

মৌলিক কার্যকলাপে তাদের প্রয়োগ করুন। আমি প্রোগ্রেস বার এবং প্লেসহোল্ডারে আইডি সেটিং ফাংশন তৈরি করেছি এবং এই ভিউ শুরু করেছি।

protected open fun hasProgressBar(): Boolean = false

    protected abstract fun progressBarId(): Int

    protected abstract fun placeholderId(): Int

    private var vProgress: View? = null
    private var vPlaceholder: View? = null
override fun onShowProgress() {
        vProgress?.visibility = View.VISIBLE
    }

    override fun onHideProgress() {
        vProgress?.visibility = View.GONE
    }

    override fun onShowPlaceholder() {
        vPlaceholder?.visibility = View.VISIBLE
    }

    override fun onHidePlaceholder() {
        vPlaceholder?.visibility = View.INVISIBLE
    }

    public override fun onStop() {
        super.onStop()
        onHideProgress()
    }

এবং অবশেষে onCreate-এ পদ্ধতি আমি ভিউ এর জন্য একটি আইডি সেট করেছি:

if (hasProgressBar()) {
            vProgress = findViewById(progressBarId())
            vProgress?.setOnClickListener(null)
        }
        vPlaceholder = findViewById(placeholderId())
        startObserveLiveData()

আমি মৌলিক ভিউমডেল এবং মৌলিক কার্যকলাপ তৈরির বানান করেছি। বেসিক ফ্র্যাগমেন্ট একই নীতি অনুসরণ করে তৈরি করা হবে।

আপনি যখন প্রতিটি পৃথক স্ক্রীন তৈরি করেন, আপনি যদি আরও এক্সটেনশন এবং সম্ভাব্য পরিবর্তনগুলি বিবেচনা করছেন, তাহলে আপনাকে এর ভিউমডেল সহ একটি পৃথক ফ্র্যাগমেন্ট তৈরি করতে হবে।

দ্রষ্টব্য:সেক্ষেত্রে যখন খণ্ডগুলিকে একটি ক্লাস্টারে একত্রিত করা যেতে পারে, এবং ব্যবসায়িক যুক্তি একটি বিশাল জটিলতা বোঝায় না, অনেকগুলি খণ্ড একটি ভিউমডেল ব্যবহার করতে পারে৷

ক্রিয়াকলাপে প্রয়োগ করা ইন্টারফেসের কারণে ফ্র্যাগমেন্টের মধ্যে স্যুইচ করা হয়। এটি করার জন্য, প্রতিটি খণ্ডের একটি সঙ্গী বস্তু{ } থাকা উচিত৷ ফ্র্যাগমেন্ট অবজেক্ট বিল্ডিংয়ের পদ্ধতির সাথে আর্গুমেন্ট স্থানান্তর করার ক্ষমতা বান্ডলে :

companion object {
        fun newInstance() = FavoriteFragment().apply { arguments = Bundle() }
    }

আর্কিটেকচার সমাধান

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

এটি ছাড়াও, RxJava2 ব্যবহার করে টুলস আমি এটির সাথে আরও নমনীয় কাজের জন্য ডেটা রূপান্তর সংহত করেছি। এটি সার্ভারের প্রতিক্রিয়া এবং ডাটাবেসের সাথে কাজ করে উভয়ের জন্য উদ্বেগ প্রকাশ করে।

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

খণ্ডগুলি UI এর জন্য দায়ী৷ , এবং ViewModel Fragments ব্যবসায়িক যুক্তি সম্পাদনের জন্য দায়ী। যদি ব্যবসায়িক যুক্তি পুরো কার্যকলাপের সাথে উদ্বিগ্ন হয়, তাহলে ViewModel Activity.

ViewModels val … অলস{}, এর মাধ্যমে শুরু করার মাধ্যমে একটি প্রদানকারীর কাছ থেকে ডেটা পায় যদি আপনার একটি অপরিবর্তনীয় বস্তুর প্রয়োজন হয়, অথবা lateinit var, যদি তদ্বিপরীত। ব্যবসায়িক লজিক কার্যকর করার পরে, যদি আপনাকে UI, পরিবর্তন করতে ডেটা স্থানান্তর করতে হয় আপনি নতুন MutableLiveData তৈরি করেন ViewModel-এ যা আপনি observeLiveData() -এ ব্যবহার করবেন আমাদের খণ্ডের পদ্ধতি।

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

interface BaseDataConverter<IN, OUT> {

    fun convertInToOut(inObject: IN): OUT

    fun convertOutToIn(outObject: OUT): IN

    fun convertListInToOut(inObjects: List<IN>?): List<OUT>?

    fun convertListOutToIn(outObjects: List<OUT>?): List<IN>?

    fun convertOUTtoINSingleTransformer(): SingleTransformer<IN?, OUT>

    fun convertListINtoOUTSingleTransformer(): SingleTransformer<List<OUT>, List<IN>>
}

abstract class BaseDataConverterImpl<IN, OUT> : BaseDataConverter<IN, OUT> {

    override fun convertInToOut(inObject: IN): OUT = processConvertInToOut(inObject)

    override fun convertOutToIn(outObject: OUT): IN = processConvertOutToIn(outObject)

    override fun convertListInToOut(inObjects: List<IN>?): List<OUT> =
            inObjects?.map { convertInToOut(it) } ?: listOf()

    override fun convertListOutToIn(outObjects: List<OUT>?): List<IN> =
            outObjects?.map { convertOutToIn(it) } ?: listOf()

    override fun convertOUTtoINSingleTransformer() =
            SingleTransformer<IN?, OUT> { it.map { convertInToOut(it) } }

    override fun convertListINtoOUTSingleTransformer() =
            SingleTransformer<List<OUT>, List<IN>> { it.map { convertListOutToIn(it) } }

    protected abstract fun processConvertInToOut(inObject: IN): OUT

    protected abstract fun processConvertOutToIn(outObject: OUT): IN
}

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

চলুন শুরু করা যাক নেটওয়ার্ক দিয়ে - এর সাথে RestClient। retrofitBuilder পদ্ধতিটি নিম্নরূপ হবে:

fun retrofitBuilder(): Retrofit = Retrofit.Builder()
            .baseUrl(BASE_URL)
            .addCallAdapterFactory(RxJava2CallAdapterFactory.create())
            .addConverterFactory(NullOrEmptyConverterFactory().converterFactory())
            .addConverterFactory(GsonConverterFactory.create(createGsonBuilder()))
            .client(createHttpClient())
            .build()
//base url
    const val BASE_URL = "https://newsapi.org"

তৃতীয় পক্ষের API ব্যবহার করে, সার্ভার থেকে সর্বদা একটি সম্পূর্ণ শূন্য প্রতিক্রিয়া পাওয়ার সুযোগ থাকে এবং এর জন্য প্রচুর কারণ থাকতে পারে। এজন্য একটি অতিরিক্ত NullOrEmptyConverterFactory পরিস্থিতি সামাল দিতে সাহায্য করবে। এটি দেখতে এইরকম:

class NullOrEmptyConverterFactory : Converter.Factory() {

    fun converterFactory() = this

    override fun responseBodyConverter(type: Type?,
                                       annotations: Array<Annotation>,
                                       retrofit: Retrofit): Converter<ResponseBody, Any>? {
        return Converter { responseBody ->
            if (responseBody.contentLength() == 0L) {
                null
            } else {
                type?.let {
                    retrofit.nextResponseBodyConverter<Any>(this, it, annotations)?.convert(responseBody) }
            }
        }
    }
}

মডেল তৈরি করতে, একটি API তৈরি করা প্রয়োজন। উদাহরণ হিসেবে, আমি newsapi.org থেকে অ-বাণিজ্যিক ব্যবহারের জন্য বিনামূল্যে APU ব্যবহার করব। এটিতে অনুরোধকৃত কার্যকারিতার একটি বরং বিস্তৃত তালিকা রয়েছে, তবে আমি এই উদাহরণের জন্য একটি ছোট অংশ ব্যবহার করব। দ্রুত রেজিস্ট্রেশন করার পর, আপনি API এবং আপনার api কী -তে অ্যাক্সেস পাবেন যা প্রতিটি অনুরোধের জন্য প্রয়োজনীয়।

শেষ পয়েন্ট হিসাবে, আমি https://newsapi.org/v2/everything ব্যবহার করব প্রস্তাবিত কোয়েরি থেকে আমি নিম্নলিখিত নির্বাচন করি:q - অনুসন্ধান ক্যোয়ারী, থেকে - তারিখ থেকে বাছাই, থেকে - তারিখে সাজানো, বাছাই - নির্বাচিত মানদণ্ড অনুসারে বাছাই করা, এবং apiKey থাকতে হবে

RestClient এর পরে সৃষ্টি, আমি আমাদের অ্যাপের জন্য নির্বাচিত ক্যোয়ারী সহ একটি API ইন্টারফেস তৈরি করি:

interface NewsApi {
    @GET(ENDPOINT_EVERYTHING)
    fun getNews(@Query("q") searchFor: String?,
                @Query("from") fromDate: String?,
                @Query("to") toDate: String?,
                @Query("sortBy") sortBy: String?,
                @Query("apiKey") apiKey: String?): Single<NewsNetworkModel>
}
//endpoints
    const val ENDPOINT_EVERYTHING = "/v2/everything"

আমরা NewsNetworkModel:

-এ এই প্রতিক্রিয়াটি পাব
data class NewsNetworkModel(@SerializedName("articles")
                            var articles: List<ArticlesNetworkModel>? = listOf())
data class ArticlesNetworkModel(@SerializedName("title")
                                var title: String? = null,
                                @SerializedName("description")
                                var description: String? = null,
                                @SerializedName("urlToImage")
                                var urlToImage: String? = null)

পুরো প্রতিক্রিয়া থেকে এই তথ্যগুলি একটি ছবি, শিরোনাম এবং সংবাদের বিবরণ সহ একটি তালিকা প্রদর্শন করার জন্য যথেষ্ট হবে৷

আমাদের স্থাপত্য পদ্ধতির বাস্তবায়নের জন্য, আসুন সাধারণ মডেল তৈরি করি:

interface News {
    var articles: List<Article>?
}

class NewsModel(override var articles: List<Article>? = null) : News
interface Article {
    var id: Long?
    var title: String?
    var description: String?
    var urlToImage: String?
    var isAddedToFavorite: Boolean?
    var fragmentName: FragmentsNames?
}

class ArticleModel(override var id: Long? = null,
                   override var title: String? = null,
                   override var description: String? = null,
                   override var urlToImage: String? = null,
                   override var isAddedToFavorite: Boolean? = null,
                   override var fragmentName: FragmentsNames? = null) : Article

যেহেতু আর্টিকেল মডেলটি অ্যাডাপ্টারে ডেটাবেস এবং ডেটা প্রদর্শনের সাথে সংযোগের জন্য ব্যবহার করা হবে, তাই আমাদের 2টি মার্জিন যোগ করতে হবে যা আমি তালিকার UI উপাদানগুলি পরিবর্তন করার জন্য ব্যবহার করব।

যখন অনুরোধের জন্য সবকিছু প্রস্তুত হয়, আমি নেটওয়ার্ক মডেলগুলির জন্য রূপান্তরকারী তৈরি করি যা আমরা নেটওয়ার্কমডিউলের মাধ্যমে প্রাপ্ত সংবাদের প্রশ্নে ব্যবহার করব৷

কনভার্টারগুলি নেস্টিং থেকে বিপরীত ক্রমে তৈরি হয় এবং তারা সেই অনুযায়ী সরাসরি ক্রমে রূপান্তর করে। তাই প্রথমটি আমি নিবন্ধে তৈরি করি, দ্বিতীয়টি নিউজে:

interface ArticlesBeanConverter

class ArticlesBeanDataConverterImpl : BaseDataConverterImpl<ArticlesNetworkModel, Article>(), ArticlesBeanConverter {

    override fun processConvertInToOut(inObject: ArticlesNetworkModel): Article = inObject.run {
        ArticleModel(null, title, description, urlToImage, false, FragmentsNames.NEWS)
    }

    override fun processConvertOutToIn(outObject: Article): ArticlesNetworkModel = outObject.run {
        ArticlesNetworkModel(title, description, urlToImage)
    }
}
interface NewsBeanConverter

class NewsBeanDataConverterImpl : BaseDataConverterImpl<NewsNetworkModel, News>(), NewsBeanConverter {

    private val articlesConverter by lazy { ArticlesBeanDataConverterImpl() }

    override fun processConvertInToOut(inObject: NewsNetworkModel): News = inObject.run {
        NewsModel(articles?.let { articlesConverter.convertListInToOut(it) })
    }

    override fun processConvertOutToIn(outObject: News): NewsNetworkModel = outObject.run {
        NewsNetworkModel(articles?.let { articlesConverter.convertListOutToIn(it) })
    }
}

আপনি উপরে দেখতে পাচ্ছেন, নিউজ অবজেক্ট রূপান্তরের সময়, আর্টিকেল অবজেক্টের তালিকার রূপান্তরও কার্যকর করা হয়।

একবার নেটওয়ার্ক মডেলের জন্য রূপান্তরকারী তৈরি হয়ে গেলে, আসুন মডিউল (রিপোজিটরি নেটওয়ার্ক) তৈরিতে এগিয়ে যাই। যেহেতু সাধারণত 1 বা 2টির বেশি ইন্টারফেস API আছে, তাই আপনাকে বেসমডিউল, টাইপ করা API, নেটওয়ার্ক মডিউল এবং রূপান্তর মডেল তৈরি করতে হবে৷

এটি দেখতে এইরকম:

abstract class BaseNetworkModule<A, NM, M>(val api: A, val dataConverter: BaseDataConverter<NM, M>)

তদনুসারে, এটি নিউজমডিউলে নিম্নলিখিত হবে:

interface NewsModule {

    fun getNews(fromDate: String? = null, toDate: String? = null, sortBy: String? = null): Single<News>
}

class NewsModuleImpl(api: NewsApi) : BaseNetworkModule<NewsApi, NewsNetworkModel, News>(api, NewsBeanDataConverterImpl()), NewsModule {

    override fun getNews(fromDate: String?, toDate: String?, sortBy: String?): Single<News> =
            api.getNews(searchFor = SEARCH_FOR, fromDate = fromDate, toDate = toDate, sortBy = sortBy, apiKey = API_KEY)
                    .compose(dataConverter.convertOUTtoINSingleTransformer())
                    .onErrorResumeNext(NetworkErrorUtils.rxParseError())
}

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

আপনি উপরে দেখতে পাচ্ছেন, আমি প্রতিক্রিয়া প্রক্রিয়াকরণের সময় ডেটা রূপান্তর প্রয়োগ করেছি।

ডাটাবেস নিয়ে কাজ করি। আমি অ্যাপ ডাটাবেস তৈরি করি, এটিকে AppDatabase বলি এবং রুমডেটাবেস() থেকে উত্তরাধিকারী .

ডাটাবেস আরম্ভ করার জন্য, ডেটাবেস ক্রিয়েটর তৈরি করা প্রয়োজন , যা অ্যাপ-এ আরম্ভ করা উচিত ক্লাস।

object DatabaseCreator {

    lateinit var database: AppDatabase
    private val isDatabaseCreated = MutableLiveData<Boolean>()
    private val mInitializing = AtomicBoolean(true)

    @SuppressWarnings("CheckResult")
    fun createDatabase(context: Context) {
        if (mInitializing.compareAndSet(true, false).not()) return
        isDatabaseCreated.value = false
        Completable.fromAction { database = Room.databaseBuilder(context, AppDatabase::class.java, DB_NAME).build() }
                .compose { completableToMain(it) }
                .subscribe({ isDatabaseCreated.value = true }, { it.printStackTrace() })
    }
}

এখন onCreate()-এ অ্যাপ এর পদ্ধতি ক্লাস আমি শুরু করি Stetho এবং ডাটাবেস:

override fun onCreate() {
        super.onCreate()
        instance = this
        Stetho.initializeWithDefaults(this)
        DatabaseCreator.createDatabase(this)
    }

যখন ডাটাবেস তৈরি করা হয়, আমি ভিতরে একটি একক সন্নিবেশ() পদ্ধতি সহ একটি মৌলিক Dao তৈরি করি:

@Dao
interface BaseDao<in I> {

    @Insert(onConflict = OnConflictStrategy.REPLACE)
    fun insert(obj: I)
}

আমাদের অ্যাপের ধারণার উপর ভিত্তি করে, আমি আমার পছন্দের সংবাদ সংরক্ষণ করব, সংরক্ষিত নিবন্ধগুলির তালিকা পেতে, সংরক্ষিত সংবাদগুলি এর আইডি দ্বারা মুছে ফেলব, বা টেবিল থেকে সমস্ত খবর মুছে ফেলব। আমাদের NewsDao নিম্নলিখিত হবে:

@Dao
interface NewsDao : BaseDao<NewsDatabase> {

    @Query("SELECT * FROM $NEWS_TABLE")
    fun getNews(): Single<List<NewsDatabase>>

    @Query("DELETE FROM $NEWS_TABLE WHERE id = :id")
    fun deleteNewsById(id: Long)

    @Query("DELETE FROM $NEWS_TABLE")
    fun deleteFavoriteNews()
}

এবং সংবাদ টেবিলটি নিম্নরূপ হবে:

@Entity(tableName = NEWS_TABLE)
data class NewsDatabase(@PrimaryKey var id: Long?,
                        var title: String?,
                        var description: String?,
                        var urlToImage: String?)

টেবিলটি তৈরি হয়ে গেলে, এটি একটি ডাটাবেসের সাথে লিঙ্ক করা যাক:

@Database(entities = [NewsDatabase::class], version = DB_VERSION)
abstract class AppDatabase : RoomDatabase() {

    abstract fun newsDao(): NewsDao
}

এখন আমরা ডাটাবেসের সাথে কাজ করতে পারি, এটি থেকে ডেটা সংরক্ষণ এবং বের করতে পারি।

মডিউলের জন্য (রিপোজিটরি নেটওয়ার্ক), আমি একটি মডেল রূপান্তরকারী তৈরি করব - ডাটাবেস টেবিল মডেল:

interface NewsDatabaseConverter

class NewsDatabaseDataConverterImpl : BaseDataConverterImpl<Article, NewsDatabase>(), NewsDatabaseConverter {

    override fun processConvertInToOut(inObject: Article): NewsDatabase =
            inObject.run {
                NewsDatabase(id, title, description, urlToImage)
            }

    override fun processConvertOutToIn(outObject: NewsDatabase): Article =
            outObject.run {
                ArticleModel(id, title, description, urlToImage, true, FragmentsNames.FAVORITES)
            }
}

BaseRepository বিভিন্ন টেবিলের সাথে কাজ করার জন্য উপলব্ধ। আসুন এটি লিখি। এটির সহজতম সংস্করণে নিচের মত দেখাবে যা অ্যাপের জন্য যথেষ্ট:

abstract class BaseRepository<M, DBModel> {

    protected abstract val dataConverter: BaseDataConverter<M, DBModel>
    protected abstract val dao: BaseDao<DBModel>
}

BaseRepository তৈরি করার পর, আমি NewsRepository তৈরি করব :

interface NewsRepository {

    fun saveNew(article: Article): Single<Article>

    fun getSavedNews(): Single<List<Article>>

    fun deleteNewsById(id: Long): Single<Unit>

    fun deleteAll(): Single<Unit>
}

object NewsRepositoryImpl : BaseRepository<Article, NewsDatabase>(), NewsRepository {

    override val dataConverter by lazy { NewsDatabaseDataConverterImpl() }
    override val dao by lazy { DatabaseCreator.database.newsDao() }

    override fun saveNew(article: Article): Single<Article> =
            Single.just(article)
                    .map { dao.insert(dataConverter.convertInToOut(it)) }
                    .map { article }

    override fun getSavedNews(): Single<List<Article>> =
            dao.getNews().compose(dataConverter.convertListINtoOUTSingleTransformer())

    override fun deleteNewsById(id: Long): Single<Unit> =
            Single.just(dao.deleteNewsById(id))

    override fun deleteAll(): Single<Unit> =
            Single.just(dao.deleteFavoriteNews())
}

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

abstract class BaseProvider<NM, DBR> {

    val repository: DBR = this.initRepository()

    val networkModule: NM = this.initNetworkModule()

    protected abstract fun initRepository(): DBR

    protected abstract fun initNetworkModule(): NM
}


তারপর NewsProvider নিচের মত দেখাবে:

interface NewsProvider {

    fun loadNewsFromServer(fromDate: String? = null, toDate: String? = null, sortBy: String? = null): Single<News>

    fun saveNewToDB(article: Article): Single<Article>

    fun getSavedNewsFromDB(): Single<List<Article>>

    fun deleteNewsByIdFromDB(id: Long): Single<Unit>

    fun deleteNewsFromDB(): Single<Unit>
}

object NewsProviderImpl : BaseProvider<NewsModule, NewsRepositoryImpl>(), NewsProvider {

    override fun initRepository() = NewsRepositoryImpl

    override fun initNetworkModule() = NewsModuleImpl(RestClient.retrofitBuilder().create(NewsApi::class.java))

    override fun loadNewsFromServer(fromDate: String?, toDate: String?, sortBy: String?) = networkModule.getNews(fromDate, toDate, sortBy)

    override fun saveNewToDB(article: Article) = repository.saveNew(article)

    override fun getSavedNewsFromDB() = repository.getSavedNews()

    override fun deleteNewsByIdFromDB(id: Long) = repository.deleteNewsById(id)

    override fun deleteNewsFromDB() = repository.deleteAll()
}

এখন আমরা সহজেই খবরের তালিকা পেয়ে যাব। NewsViewModel -এ আমরা আরও ব্যবহারের জন্য আমাদের প্রদানকারীর সমস্ত পদ্ধতি ঘোষণা করব:

val loadNewsSuccessLiveData = MutableLiveData<News>()
    val loadLikedNewsSuccessLiveData = MutableLiveData<List<Article>>()
    val deleteLikedNewsSuccessLiveData = MutableLiveData<Boolean>()

    private val loadNewsSuccessConsumer = Consumer<News> { loadNewsSuccessLiveData.value = it }
    private val loadLikedNewsSuccessConsumer = Consumer<List<Article>> { loadLikedNewsSuccessLiveData.value = it }
    private val deleteLikedNewsSuccessConsumer = Consumer<Unit> { deleteLikedNewsSuccessLiveData.value = true }

    private val dataProvider by lazy { NewsProviderImpl }

    init {
        isLoadingLiveData.apply { addSource(loadNewsSuccessLiveData) { value = false } }
@SuppressLint("CheckResult")
    fun loadNews(fromDate: String? = null, toDate: String? = null, sortBy: String? = null) {
        isLoadingLiveData.value = true
        isEmptyDataPlaceholderLiveData.value = false
        dataProvider.loadNewsFromServer(fromDate, toDate, sortBy)
                .compose(RxUtils.ioToMainTransformer())
                .subscribe(loadNewsSuccessConsumer, onErrorConsumer)

    }

    @SuppressLint("CheckResult")
    fun saveLikedNew(article: Article) {
        Single.fromCallable { Unit }
                .flatMap { dataProvider.saveNewToDB(article) }
                .compose(RxUtils.ioToMainTransformerSingle())
                .subscribe({}, { onErrorConsumer })
    }

    @SuppressLint("CheckResult")
    fun removeLikedNew(id: Long) {
        Single.fromCallable { Unit }
                .flatMap { dataProvider.deleteNewsByIdFromDB(id) }
                .compose(RxUtils.ioToMainTransformerSingle())
                .subscribe({}, { onErrorConsumer })
    }

    @SuppressLint("CheckResult")
    fun loadLikedNews() {
        Single.fromCallable { Unit }
                .flatMap { dataProvider.getSavedNewsFromDB() }
                .compose(RxUtils.ioToMainTransformerSingle())
                .subscribe(loadLikedNewsSuccessConsumer, onErrorConsumer)
    }

    @SuppressLint("CheckResult")
    fun removeLikedNews() {
        Single.fromCallable { Unit }
                .flatMap { dataProvider.deleteNewsFromDB() }
                .compose(RxUtils.ioToMainTransformerSingle())
                .subscribe(deleteLikedNewsSuccessConsumer, onErrorConsumer)
    }

ViewModel-এ ব্যবসার লজিক কার্যকর করার সমস্ত পদ্ধতি ঘোষণা করার পর, আমরা সেগুলিকে ফ্র্যাগমেন্ট থেকে ফেরত পাঠাব যেখানে observeLiveData() ঘোষিত প্রতিটি ফলাফল লাইভডেটা প্রক্রিয়া করা হবে৷

এটি সহজে বাস্তবায়ন করতে, SEARCH_FOR -এ প্যারামিটারগুলি আমি এলোমেলোভাবে Apple, বেছে নিয়েছি এবং আরও বাছাই করা হবে জনপ্রিয়তা দ্বারা ট্যাগ প্রয়োজনে, আপনি এই পরামিতিগুলি পরিবর্তন করার জন্য ন্যূনতম কার্যকারিতা যোগ করতে পারেন।

যেহেতু newsapi.org আপনাকে একটি নিউজ আইডি প্রদান করে না, তাই আমি আইডি হিসাবে উপাদান সূচী গ্রহণ করি। জনপ্রিয়তা ট্যাগ অনুসারে বাছাই করাও API এর মাধ্যমে প্রয়োগ করা হয়। কিন্তু জনপ্রিয়তা অনুসারে বাছাই করার সময় বেসে একই আইডি দিয়ে ডেটা পুনঃলিখন এড়াতে, আমি সংবাদ তালিকা লোড করার আগে বেসে ডেটা উপলব্ধতা যাচাই করব। যদি বেস খালি থাকে - নতুন তালিকা লোড হচ্ছে, যদি না হয় - বিজ্ঞপ্তি দেখানো হয়।

চলুন onViewCreated() -এ কল করি NewsFragment এর পদ্ধতি নিম্নলিখিত পদ্ধতি:

private fun loadLikedNews() {
        viewModel.loadLikedNews()
    }

যেহেতু আমাদের বেস খালি, পদ্ধতি loadNews() চালু করা হবে। অবজারভলাইভডেটা -এ আমি আমাদের লাইভডেটা লোড করার পদ্ধতি ব্যবহার করব - viewModel.loadNewsSuccessLiveData.observe(..){news →}, যেখানে অনুরোধ সফল হলে আমরা সংবাদ নিবন্ধের তালিকা পাব এবং তারপর অ্যাডাপ্টারে স্থানান্তর করব:

isEmptyDataPlaceholderLiveData.value = news.articles?.isEmpty()
                with(newsAdapter) {
                    news.articles?.toMutableList()?.let {
                        clear()
                        addAll(it)
                    }
                    notifyDataSetChanged()
                }
                loadNewsSuccessLiveData.value = null

অ্যাপটি চালু করার পরে, আপনি নিম্নলিখিত ফলাফল দেখতে পাবেন:

আপনার অ্যান্ড্রয়েড অ্যাপস আর্কিটেকচারকে কীভাবে সহজ করবেন:কোড নমুনা সহ একটি বিশদ নির্দেশিকা

ডানদিকে টুলবার মেনুতে, আপনি 2টি বিকল্প দেখতে পারেন - সাজানো এবং পছন্দসই। জনপ্রিয়তা অনুসারে তালিকাটি সাজান এবং নিম্নলিখিত ফলাফল পান:

আপনার অ্যান্ড্রয়েড অ্যাপস আর্কিটেকচারকে কীভাবে সহজ করবেন:কোড নমুনা সহ একটি বিশদ নির্দেশিকা

আপনি যদি ফেভারিটে যান, আপনি শুধুমাত্র একটি প্লেসহোল্ডার দেখতে পাবেন, যেহেতু বেসে কোনো ডেটা নেই। ফেভারিট স্ক্রীনটি নিচের মত দেখাবে:

আপনার অ্যান্ড্রয়েড অ্যাপস আর্কিটেকচারকে কীভাবে সহজ করবেন:কোড নমুনা সহ একটি বিশদ নির্দেশিকা

পছন্দের UI খণ্ডটিতে পছন্দের খবরের তালিকা প্রদর্শনের জন্য একটি স্ক্রীন রয়েছে এবং ডেটাবেস পরিষ্কারের জন্য টুলবারে শুধুমাত্র একটি বিকল্প রয়েছে। আপনি যখন "লাইক"-এ ক্লিক করে ডেটা সংরক্ষণ করবেন, তখন স্ক্রিনগুলি নিম্নলিখিতগুলির মতো দেখাবে:

আপনার অ্যান্ড্রয়েড অ্যাপস আর্কিটেকচারকে কীভাবে সহজ করবেন:কোড নমুনা সহ একটি বিশদ নির্দেশিকা

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

var isAddedToFavorite: Boolean?
    var fragmentName: FragmentsNames?

আপনি আবার "লাইক" ক্লিক করলে, সংরক্ষিত উপাদানটি বেস থেকে সরানো হবে।

র্যাপিং আপ

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

আমি আপনাকে যে আর্কিটেকচার দিয়েছি এবং মিস্টার মার্টিনের ক্লিন আর্কিটেকচারের মধ্যে পার্থক্য কী? একেবারে শুরুতে, আমি লক্ষ্য করেছি যে আমার স্থাপত্যটি CA-এর অনুরূপ কারণ এটি ভিত্তি হিসাবে ব্যবহৃত হয়। এখানে নীচে CA স্কিম রয়েছে:

আপনার অ্যান্ড্রয়েড অ্যাপস আর্কিটেকচারকে কীভাবে সহজ করবেন:কোড নমুনা সহ একটি বিশদ নির্দেশিকা

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

নীচের স্কিমে, নিয়ন্ত্রক InputPort থেকে কলের পদ্ধতি যা UseCase প্রয়োগ করে , এবং আউটপুট পোর্ট ইন্টারফেস এই প্রতিক্রিয়া এবং উপস্থাপক গ্রহণ করে এটা বাস্তবায়ন করে। UseCase এর পরিবর্তে সরাসরি উপস্থাপক, এর উপর নির্ভর করে এটি তার স্তরগুলির ইন্টারফেসের উপর নির্ভর করে এবং এটি নির্ভরতার নিয়ম, এর সাথে বিরোধিতা করে না এবং উপস্থাপকের এই ইন্টারফেসটি বাস্তবায়ন করা উচিত।

আপনার অ্যান্ড্রয়েড অ্যাপস আর্কিটেকচারকে কীভাবে সহজ করবেন:কোড নমুনা সহ একটি বিশদ নির্দেশিকা

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

গেটওয়েস? সম্পর্কে কি আমি যেমনটা দেখছি, গেটওয়ে ডাটাবেসের সাথে কাজ করার জন্য একটি সংগ্রহস্থল এবং একটি নেটওয়ার্কের সাথে কাজ করার জন্য একটি মডিউল। আমরা কন্ট্রোলার থেকে পরিত্রাণ পেয়েছি যেহেতু প্রাথমিকভাবে ক্লিন আর্কিটেকচার তৈরি করা হয়েছিল উচ্চ জটিলতার ব্যবসায়িক অ্যাপ গঠনের জন্য, এবং ডেটা কনভার্টারগুলি আমার অ্যাপে এর কার্য সম্পাদন করে। ViewModels উপস্থাপকদের প্রতিস্থাপন করে UI প্রক্রিয়াকরণের জন্য ফ্র্যাগমেন্টে ডেটা স্থানান্তর করে।

আমার পদ্ধতিতে, আমি কঠোরভাবে নির্ভরতার নিয়ম মেনে চলি, এবং রিপোজিটরি, মডিউল, মডেল এবং প্রদানকারীর যুক্তি এনক্যাপসুলেটেড, এবং ইন্টারফেসের মাধ্যমে তাদের অ্যাক্সেস সম্ভব। সুতরাং, বাহ্যিক স্তরগুলির পরিবর্তনগুলি অভ্যন্তরীণ স্তরগুলিকে প্রভাবিত করে না। এবং RxJava2 ব্যবহার করে বাস্তবায়ন প্রক্রিয়া , KotlinRx , এবং কোটলিন লাইভডেটা ডেভেলপারের কাজগুলিকে সহজ, পরিষ্কার করে এবং কোড ভালভাবে পড়া এবং সহজেই এক্সটেনসিবল হয়ে ওঠে৷


  1. কিভাবে আপনার Android ফোনকে Windows 10 এর সাথে লিঙ্ক করবেন?

  2. কিভাবে আপনার অ্যান্ড্রয়েড ফোনে অ্যাপগুলি আনইনস্টল বা মুছবেন

  3. এন্ড্রয়েডে আপনার ডিফল্ট অ্যাপগুলি কীভাবে পরিবর্তন করবেন

  4. অ্যান্ড্রয়েডে বন্ধুদের সাথে আপনার অবস্থান কীভাবে শেয়ার করবেন