কম্পিউটার টিউটোরিয়াল

Android অ্যাপ আর্কিটেকচার সহজীকরণ: কোড নমুনা সহ ব্যাবহারিক গাইড

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

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

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

এই মুহূর্তে, Google-এর 덕분에 Android ডেভেলপারদের জন্য ১৬টি ভিন্ন আর্কিটেকচার রয়েছে:

  • ৬টি স্থিতিশীল নমুনা (Java);
  • ২টি স্থিতিশীল নমুনা (Kotlin):
  • ৪টি এক্সটার্নাল নমুনা;
  • ৩টি ডিপ্রিকেটেড নমুনা;
  • ১টি প্রগতিশীল নমুনা।

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

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

প্রক্রিয়া সময়ে ব্যবহৃত টুলস

এই সব আর্কিটেকচার অধ্যয়ন করার পর, আমি একটি সহজ পদ্ধতি বিল্ড করেছি এবং কম লেয়ার সহ একটি আর্কিটেকচার নিয়ে এসেছি। আমি আপনাকে দেখাব কীভাবে একটি সিম্পল Android অ্যাপ ইমপ্লিমেন্ট করতে হয় যা নিউজ লিস্ট লোড করে, স্টোরিজ ফেভারিটসে সেভ করতে দেয়, এবং প্রয়োজনে ডিলিট করতে দেয় আমার পদ্ধতিটি ব্যবহার করে।

Android অ্যাপ আর্কিটেকচার সহজীকরণ: কোড নমুনা সহ ব্যাবহারিক গাইড

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

  • Kotlin অ্যাপ ডেভেলপ করতে AndroidX লাইব্রেরির সাথে
  • Room SQLite ডেটাবেস হিসেবে
  • Stetho ডেটাবেস ব্রাউজ করার জন্য
  • Retrofit2 RxJava2-এর সাথে সার্ভার রিকোয়েস্ট লগ করতে এবং সার্ভার রেসপন্স পেতে।
  • Glide ইমেজ প্রসেস করতে
  • Android Architecture Components (LiveData, ViewModel, Room) এবং ReactiveX (RxJava2, RxKotlin এবং RxAndroid) ডিপেন্ডেন্সি বিল্ড করতে, ডাইনামিক ডেটা পরিবর্তন, এবং অ্যাসিনক্রোনাসিটি প্রসেস করার জন্য।  

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

চলুন শুরু করি

প্রাথমিক পদক্ষেপ

AndroidX কানেক্ট করুন। অ্যাপ লেভেলের gradle.properties-এ, নিচের কোড লিখুন:

android.enableJetifier=true android.useAndroidX=true

এখন build.gradle -এ অ্যাপ মডিউল লেভেলে Android থেকে AndroidX-এ ডিপেন্ডেন্সি রিপ্লেস করতে হবে। আপনাকে সব ডিপেন্ডেন্সি ext, -এ এক্সট্রাক্ট করা উচিত, যেমন আপনি Kotlin out-of-the-box ভার্সনিং-এর উদাহরণ দেখতে পাচ্ছেন build.gradle -এ অ্যাপ লেভেলে। এবং তারপর আমি সেখানে 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 } }

অন্যান্য সব ডিপেন্ডেন্সির জন্য, আমি এর ext ফাইল বিল্ড করব, যেখানে আমি SDK ভার্সন সহ সব ডিপেন্ডেন্সি যোগ করব, ভার্সনিং ভাগ করব এবং ডিপেন্ডেন্সি অ্যারে তৈরি করব যা আরও 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" ] }

ভার্সন এবং অ্যারে নাম যাদমতো ইমপ্লিমেন্ট করা হয়েছে। এরপর, আমরা 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 নামহীন অ্যাপ দেখতে পাবে না এবং আপনি ডেটাবেসে তাকাতে পারবেন না।

বেসিক কম্পোনেন্ট বিল্ডিং

দ্রষ্টব্য যে MVVM (Model-View-ViewModel) প্যাটার্নটি এই আর্কিটেকচার বিল্ড করার ভিত্তি হিসেবে ব্যবহার করা হয়েছে।

চলুন ডেভেলপমেন্ট শুরু করি। প্রথমে আপনাকে একটি ক্লাস তৈরি করতে হবে যা Application()-কে ইনহেরিট করবে। এই ক্লাসে, আমরা অ্যাপ কন্টেক্সটে অ্যাক্সেস দেব তার পরবর্তী ব্যবহার জন্য।

@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) } }

দ্বিতীয় ধাপ হল বেসিক অ্যাপ কম্পোনেন্ট তৈরি করা শুরু করে ViewModel থেকে, jota আমি প্রতিটি Activity বা Fragment-এ ব্যবহার করব।

abstract class BaseViewModel constructor(app: Application) : AndroidViewModel(app) { override fun onCleared() { super.onCleared() } }

এই অ্যাপে জটিল ফাংশনালিটি নেই। কিন্তু বেসিক ViewModel-এ আমরা ৩টি মূল LiveData রাখব:

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

ফাংশন ইমপ্লিমেন্টেশনের ফলাফল LiveData-এ ট্রান্সফার করার জন্য আমি Consumer ব্যবহার করব।

অ্যাপের যেকোনো জায়গায় এরর প্রসেস করার জন্য, আপনাকে একটি Consumer তৈরি করতে হবে যা Throwable.message মান errorLiveData-এ ট্রান্সফার করবে।

এছাড়া, বেসিক ViewModel-এ, আপনাকে একটি মেথড তৈরি করতে হবে যা একটি LiveData লিস্ট রিসিভ করবে প্রোগ্রেস বার ডিসপ্লে করার জন্য তাদের ইমপ্লিমেন্টেশন সময়।

আমাদের বেসিক ViewModel এরূপ দেখাবে:

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 } } }


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

আমাদের অ্যাপ ১টি Activity এবং ২টি Fragment-এ বিল্ট হবে যা Container activity-এ ইনফ্লেট হবে। আমাদের Activity-এর 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>

আমাদের BaseActivity নিচের মতো:

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) } }

চলুন সকল ভবিষ্যৎ Activity-এ প্রসেসিং এরর ডিসপ্লে করার পদ্ধতি ইমপ্লিমেন্ট করি। আমি সিম্পলिसিটি জন্য সাধারণ Toast-এ করব।

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

এবং এই এরর টেক্সটকে ডিসপ্লে মেথডে পাঠাব:

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

বেসিক Activity-এ আমি বেসিক View Model-এ অবস্থিত errorLiveData ভ্যালু পরিবর্তন পর্যবেক্ষণ শুরু করব। startObserveLiveData() মেথড নিচের মতো হবে:

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

এখন বেসিক ViewModel-এর onErrorConsumer -কে onError প্রসেসর হিসেবে ব্যবহার করে, আপনি ইমপ্লিমেন্টেড মেথড এরর সম্পর্কে মেসেজ দেখবেন।

Back Stack-এ যোগ করার ক্ষমতা সহ Activity-এ Fragment রিপ্লেস করার একটি মেথড তৈরি করুন।

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() }

বেসিক Activity-এ ইমপ্লিমেন্ট করুন। আমি প্রগ্রেস বার এবং প্লেসহোল্ডারের ID সেট করার ফাংশন তৈরি করেছি, এবং এই Views ইনিশিয়ালাইজ করেছি।

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 মেথডে আমি View-এর জন্য ID সেট করি:

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

আমি বেসিক ViewModel এবং Basic Activity তৈরির বর্ণনা করেছি। Basic Fragment সembler প্রিন্সিপ্যাল অনুসরণ করে তৈরি হবে।

প্রতিটি পৃথক স্ক্রিন তৈরি করার সময়, যদি আপনি ভবিষ্যতে এক্সটেনশন এবং সম্ভাব্য পরিবর্তন বিবেচনা করেন, তবে আপনাকে তার নিজস্ব ViewModel সহ একটি পৃথক Fragment তৈরি করতে হবে।

নোট: যে কেসে Fragment গুলো একসাথে ক্লাস্টার করা যায়, এবং বিজনেস লজিক বড় জটিলতা ইমপ্লাই করে না, বেশ কয়েকটি Fragment একটি ViewModel ব্যবহার করতে পারে।

Fragment এর মধ্যে স্যুইচিং হয় Activity-এ ইমপ্লিমেন্ট করা ইন্টারফেসগুলো কারণ। এটি করার জন্য, প্রতিটি Fragment-এ একটি companion object{ } রয়েছে Bundle-এ আর্গুমেন্ট ট্রান্সফার করার ক্ষমতা সহ Fragment অবজেক্ট বিল্ড করার মেথড:

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

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

বেসিক কম্পোনেন্ট তৈরি হলে, আর্কিটেকচার ওপর ফোকাস করার সময়। স্কিমেটিক্যালি এটি রবার্ট সি. মার্টিন বা অনকেল ববের তৈরি ক্লিন আর্কিটেকচারের মত দেখাবে। কিন্তু যেহেতু আমি RxJava2 ব্যবহার করি, আমি Boundaries ইন্টারফেসগুলো (যে রस्तে Dependency Rule এক্সিকিউশন নিশ্চিত করা হয়) ছাড়িয়েছি স্ট্যান্ডার্ড Observable এবং Subscriber-এর সুবিধা।

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

প্রাইমারি মডেলের পাশাপাশি, আমি একটি সার্ভার রেসপন্স মডেল এবং Room-এর জন্য আলাদা টেবিল মডেল তৈরি করব। এই দুটি মডেলের মধ্যে ডেটা কনভার্ট করে, আপনি কনভারশন প্রসেস সময় যেকোনো পরিবর্তন করতে পারেন, সার্ভার রেসপন্স কনভার্ট করতে পারেন, এবং UI-তে ডিসপ্লে করার আগে ডেটাবেসে প্রয়োজনীয় ডেটা সেভ করতে পারেন।

Fragment দায়বদ্ধ UI-র জন্য, এবং ViewModel Fragment দায়বদ্ধ বিজনেস লজিক এক্সিকিউশনের জন্য। যদি বিজনেস লজিক পুরো activity-র সাথে সম্পর্কিত হয়, তবে ViewModel Activity।

ViewModel val … by lazy{}, দ্বারা ইনিশিয়ালাইজ করে প্রোভাইডার থেকে ডেটা পায়, যদি আপনাকে একটি ইনভেরিয়েবল অবজেক্ট দরকার হয়, অথবা lateinit var, যদি বিপরীত হয়। বিজনেস লজিক এক্সিকিউশনের পর, আপনি যদি UI, পরিবর্তন করতে ডেটা ট্রান্সফার করতে চান, তবে আপনি ViewModel-এ নতুন MutableLiveData তৈরি করবেন যা আপনি আমাদের Fragment-এর observeLiveData() মেথডে ব্যবহার করবেন।

এটি খুব সহজ শোনাচ্ছে। ইমপ্লিমেন্টেশনও सीধা।
আমাদের আর্কিটেকচারের একটি অপরিহার্য কম্পোনেন্ট হল একটি ডেটা কনভার্টার যা একটি ডেটা টাইপ থেকে অন্য ডেটা টাইপে সিম্পল কনভারশন বেসড। RxJava ডেটা স্ট্রিম কনভারশনের জন্য, SingleTransformer বা FlowableTransformer ব্যবহার করা হয় টাইপের ওপর নির্ভর করে। আমাদের অ্যাপের কেসে, ইন্টারফেস এবং কনভার্টারের অ‍্যাবস্ট্রাক্ট ক্লাস নিচের মতো:

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- থেকে নন-কমার্শিয়াল ইউজের জন্য ফ্রি API ব্যবহার করব। এর একটিuttosto ব্যাপক রিকোয়েস্টেড ফাংশনালিটির তালিকা আছে, কিন্তু এই উদাহরণের জন্য আমি একটি ছোট অংশ ব্যবহার করব। দ্রুত রেজিস্ট্রেশনের পর, আপনি API-এ অ্যাক্সেস পাবেন এবং আপনার api key পাবেন যা প্রতিটি রিকোয়েস্টের জন্য আবশ্যক।

এন্ডপয়েন্ট হিসেবে, আমি https://newsapi.org/v2/everything. ব্যবহার করব। প্রস্তাবিত query থেকে আমি নিচের موارد বেছে নিচ্ছি: q - সার্চ কোয়ারী, from - তারিখ থেকে সোর্টিং, to - তারিখ পর্যন্ত সোর্টিং, sortBy - নির্বাচিত ক্রাইটেরিয়া দ্বারা সোর্টিং, এবং অবশ্যই apiKey.

RestClient তैयার হলে, আমি নির্বাচিত Query সহ আমাদের অ্যাপের জন্য একটি 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

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

সব কিছু রিকোয়েস্টের জন্য প্রস্তুত হলে, আমি নেটওয়ার্ক মডেলের জন্য কনভার্টার তৈরি করব যা আমরা NetworkModule-এ নিউজ রিসিভ করার কোয়ারীতে ব্যবহার করব।

কনভার্টারগুলো নেস্টিং-এর রিভার্স অর্ডারে তৈরি করা হয়, এবং তারা ডায়রেক্ট অর্ডারে কনভার্ট করে। তাই প্রথমে আমি Article-এ তৈরি করি, দ্বিতীয়ে News-এ:

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) }) } }

আপনি উপরে দেখতে পারেন, News অবজেক্ট কনভারশন করার সময়, Article অবজেক্টের লিস্টের কনভারশনও এক্সিকিউট হয়।

নেটওয়ার্ক মডেলের জন্য কনভার্টার তৈরি হলে, চলুন মডিউল (রিপোজিটরি নেটওয়ার্ক) তৈরির দিকে যাই। যেহেতু সাধারণত ১ বা ২টির বেশি API ইন্টারফেস থাকে, তাই আপনাকে BaseModule, টাইপড API, Network Module, এবং ConversionModel তৈরি করতে হবে।

এটি কেমন দেখায়:

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

তदনুসারে, NewsModule-এ এটি নিচের মতো হবে:

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 key যেকোনো প্রস্তাবিত এন্ডপয়েন্ট দিয়ে রিকোয়েস্ট করার জন্য একটি গুরুত্বপূর্ণ প্যারামিটার। তাই আপনাকে নিশ্চিত করতে হবে যে অপশনাল প্যারামিটারগুলো আগে থেকে স্পেসিফাইড না থাকে, এবং সেগুলো ডিফল্টভাবে নালিফাই করা থাকে।

আপনি উপরে দেখতে পারেন, আমি রেসপন্স প্রসেসিং সময় ডেটা কনভারশন অ্যাপ্লাই করেছি।

চলুন ডেটাবেসের সাথে কাজ করি। আমি অ্যাপ ডেটাবেস তৈরি করব, সেটিকে AppDatabase নাম দেব এবং RoomDatabase()- থেকে ইনহেরিট করব।

ডেটাবেস ইনিশিয়ালাইজেশনের জন্য, DatabaseCreator তৈরি করতে হবে, যা App ক্লাসে ইনিশিয়ালাইজ করা উচিত।

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() }) } }

এখন App ক্লাসের onCreate() মেথডে আমি Stetho এবং ডেটাবেস ইনিশিয়ালাইজ করব:

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

ডেটাবেস তৈরি হলে, আমি একটি বেসিক Dao তৈরি করব যেতে একটা insert() মেথড থাকবে:

@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()) }

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

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-এ বিজনেস লজিক এক্সিকিউট করার সব মেথড ডিক্লেয়ার করার পর, আমরা সেগুলো Fragment-এ থেকে কলব্যাক করব যেখানে observeLiveData()-এ প্রতিটি ডিক্লেয়ারড LiveData -র ফলাফল প্রসেস করা হবে।

সহজেই ইমপ্লিমেন্ট করতে, SEARCH_FOR প্যারামিটারে আমি র‍্যান্ডমলি Apple বেছে নিয়েছি, এবং আরও সোর্টিং popularity ট্যাগ দ্বারা হবে। যদি প্রয়োজন হয়, তবে এই প্যারামিটার পরিবর্তন করার জন্য মিনিমাম ফাংশনালিটি যোগ করা যাবে।

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

চলুন NewsFragment -এর onViewCreated() মেথডে নিচের মেথড কল করি:

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

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

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

অ্যাপ লঞ্চ করলে আপনি নিচের ফলাফল দেখবেন:

Android অ্যাপ আর্কিটেকচার সহজীকরণ: কোড নমুনা সহ ব্যাবহারিক গাইড

টুলবার মেনুর ডান পাশে, আপনি ২টি অপশন দেখবেন - সোর্টিং এবং ফেভারিটস। চলুন লিস্টকে পপুলারিটি দ্বারা সোর্ট করি এবং নিচের ফলাফল পাই:

Android অ্যাপ আর্কিটেকচার সহজীকরণ: কোড নমুনা সহ ব্যাবহারিক গাইড

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

Android অ্যাপ আর্কিটেকচার সহজীকরণ: কোড নমুনা সহ ব্যাবহারিক গাইড

ফেভারিটস UI ফ্র্যাগমেন্টে লাইক করা নিউজ লিস্ট ডিসপ্লে করার একটি স্ক্রিন এবং টুলবারে ডেটাবেস ক্লিন করার জন্য শুধু একটি অপশন থাকে। আপনি "Like"-এ ক্লিক করে ডেটা সেভ করলে স্ক্রিনগুলো নিচের মতো দেখাবে:

Android অ্যাপ আর্কিটেকচার সহজীকরণ: কোড নমুনা সহ ব্যাবহারিক গাইড

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

var isAddedToFavorite: Boolean? var fragmentName: FragmentsNames?

আপনি যদি আবার "Like"-এ ক্লিক করেন, সেভড এলিমেন্ট বেস থেকে রিমুভ হয়ে যাবে।

উপসংহার

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

আমার দেওয়া আর্কিটেকচার এবং Mr. Martin-এর ক্লিন আর্কিটেকচারের মধ্যে পার্থক্য কী? সবার শুরুতে আমি নোট করেছি যে আমার আর্কিটেকচার CA-এর সাদৃশ্য কারণ এটি ভিত্তি হিসেবে ব্যবহৃত হয়েছে। নিচে CA স্কিম দেওয়া হল:

Android অ্যাপ আর্কিটেকচার সহজীকরণ: কোড নমুনা সহ ব্যাবহারিক গাইড

ইভেন্ট Presenter-এ যায়, এবং তারপর Use Case. Use Case Repository-কে রিকোয়েস্ট করে। Repository ডেটা পায়, Entity তৈরি করে, এবং UseCase-এ ট্রান্সফার করে। এইভাবে, Use Case সকল প্রয়োজনীয় Entity পায়। বিজনেস লজিক ইমপ্লিমেন্টেশনের পর, আপনি ফলাফল পাবেন যা Presenter-এ ফিরে আসে, এবং সেটি secara/ui-তে ট্রান্সফার করে।

নিচের স্কিমে, Controller InputPort -এর মেথড কল করে যা UseCase ইমপ্লিমেন্ট করে, এবং OutputPort ইন্টারফেস এই রেসপন্স রিসিভ করে এবং Presenter ইমপ্লিমেন্ট করে। UseCase সরাসরি Presenter-এর ওপর ডিপেন্ড করার বদলে, এটি তার লেয়ারের ইন্টারফেসের ওপর ডিপেন্ড করে, এবং এটি Dependency Rule-এর সাথে কন্ট্রাডিক্ট করে না, এবং Presenter এই ইন্টারফেস ইমপ্লিমেন্ট করা উচিত।

Android অ্যাপ আর্কিটেকচার সহজীকরণ: কোড নমুনা সহ ব্যাবহারিক গাইড

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

Gateways কী? আমার মতে, Gateways হল ডেটাবেসের সাথে কাজ করার জন্য একটি রিপোজিটরি এবং নেটওয়ার্কের সাথে কাজ করার জন্য একটি মডিউল। আমরা কন্ট্রোলার ছাড়িয়েছি কারণ প্রাথমিকভাবে ক্লিন আর্কিটেকচার হাই কমপ্লেক্সিটির বিজনেস অ্যাপ স্ট্রাকচার করার জন্য তৈরি হয়েছিল, এবং ডেটা কনভার্টারগুলো আমার অ্যাপে এর ফাংশন পালন করে। ViewModel-রা Fragment-এ UI প্রসেসিং-এর জন্য ডেটা ট্রান্সফার করে Presenter-র প্লেসে।

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


  1. অ্যান্ড্রয়েড ফোনকে উইন্ডোজ ১০-এ স্মুথলি সিঙ্ক করুন: ধাপে ধাপে গাইড

  2. আপনার অ্যান্ড্রয়েড ফোন থেকে অ্যাপ আনইনস্টল বা ডিলিট করার সহজ গাইড

  3. অ্যান্ড্রয়েডে ডিফল্ট অ্যাপ পরিবর্তন করার পূর্ণাঙ্গ গাইড

  4. অ্যান্ড্রয়েডে বন্ধুদের সাথে লোকেশন শেয়ার: ধাপে ধাপে গাইড