अलग-अलग प्रोग्रामर विभिन्न कार्यों को करने के तरीके पर अपने विचारों और विचारों सहित अपनी दृष्टि के अनुसार अपने मोबाइल ऐप विकसित करते हैं। कभी-कभी वे ऑब्जेक्ट ओरिएंटेड या फंक्शनल प्रोग्रामिंग के मुख्य सिद्धांतों की अवहेलना कर सकते हैं, जिससे डेवलपर्स के बीच भटकाव हो सकता है।
यह बुरा है - वे अपने कोड से निपटने में सक्षम नहीं होंगे। और अगला डेवलपर जिसे परियोजना को बनाए रखने या इसे संशोधित करने की आवश्यकता है, वह पागल हो सकता है। ऐसी परियोजनाओं को खरोंच से पुनर्निर्माण करना बेहतर है, क्योंकि रखरखाव एक जटिल प्रक्रिया बन जाती है।
जब तक Google ने अपना पहला समर्थित आर्किटेक्चर जारी नहीं किया, तब तक लगभग हर सॉफ्टवेयर डेवलपमेंट कंपनी अपने स्वयं के आर्किटेक्चर का उपयोग करती थी। इससे उन्हें अपना कोड स्पष्ट करने में मदद मिली और परियोजनाओं के बीच स्विच करना संभव हो गया। लेकिन अगर किसी डेवलपर ने कंपनियों को बदल दिया है, तो उन्हें नए प्रोजेक्ट के साथ-साथ उस नए आर्किटेक्चर को सीखने में कुछ समय लगेगा।
फिलहाल, एंड्रॉइड डेवलपर्स के लिए 16 अलग-अलग आर्किटेक्चर हैं, Google के लिए धन्यवाद:
- 6 स्थिर नमूने (जावा);
- 2 स्थिर नमूने (कोटलिन):
- 4 बाहरी नमूने;
- 3 बहिष्कृत नमूने;
- 1 नमूना प्रगति पर है।
आप जिस भी वास्तुकला का उपयोग करते हैं, वह आपके विशिष्ट उद्देश्य, दृष्टिकोण और विभिन्न प्रकार की कार्यक्षमताओं के कार्यान्वयन के लिए विभिन्न टूलकिटों के अनुप्रयोग पर निर्भर करता है। और यह प्रोग्रामिंग भाषा पर निर्भर करता है।
हालांकि, इन सभी आर्किटेक्चर में एक समान आर्किटेक्चरल नींव है जो नेटवर्क, डेटाबेस, निर्भरता और प्रसंस्करण कॉलबैक के साथ काम करने के तर्क को लगभग समान रूप से विभाजित करती है।
प्रक्रिया के दौरान उपयोग किए जाने वाले टूल
इन सभी वास्तुकलाओं का अध्ययन करने के बाद, मैंने एक सरल दृष्टिकोण बनाया और कम परतों वाली वास्तुकला के साथ आया। मैं आपको दिखाऊंगा कि एक साधारण एंड्रॉइड ऐप को कैसे कार्यान्वित किया जाए जो एक समाचार सूची लोड करता है, आपको कहानियों को पसंदीदा में सहेजने की अनुमति देता है, और यदि आवश्यक हो तो मेरे दृष्टिकोण का उपयोग करके हटा देता है।
मेरे द्वारा उपयोग की जाने वाली तकनीक का सारांश यहां दिया गया है:
- कोटलिन AndroidX . के साथ ऐप विकसित करने के लिए पुस्तकालय
- कक्ष SQLite डेटाबेस के रूप में
- स्टेथो आधारों में डेटा ब्राउज़ करने के लिए
- रेट्रोफिट2 सर्वर अनुरोधों को लॉग करने और सर्वर प्रतिक्रिया प्राप्त करने में मदद करने के लिए RxJava2 के साथ।
- ग्लाइड छवियों को संसाधित करने के लिए
- एंड्रॉइड आर्किटेक्चर घटक (लाइवडेटा, व्यूमॉडल, रूम) और ReactiveX (RxJava2, RxKotlin और RxAndroid) निर्भरता के निर्माण, गतिशील डेटा परिवर्तन और अतुल्यकालिक प्रसंस्करण के लिए।
तो यह मोबाइल ऐप टेक्नोलॉजी स्टैक है जिसका उपयोग मैंने अपने प्रोजेक्ट के लिए किया था।
आरंभ करें
पहले चरण
कनेक्ट AndroidX . gradle.properties . में ऐप स्तर पर, निम्नलिखित लिखें:
android.enableJetifier=true
android.useAndroidX=true
अब निर्भरताओं को build.gradle . में बदलना आवश्यक है Android से AndroidX तक ऐप मॉड्यूल स्तर पर। आपको सभी निर्भरताओं को 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
}
}
अन्य सभी निर्भरताओं के लिए, मैं इसके ext . का निर्माण करूंगा फ़ाइल, जहां मैं एसडीके संस्करणों सहित पूरी तरह से सभी निर्भरताओं को जोड़ता हूं, संस्करण को विभाजित करता हूं और निर्भरता द्रव्यमान बनाता हूं जिसे आगे 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" />
यदि मेनिफेस्ट में कोई नाम नहीं जोड़ा गया है, तो आपको इसे स्टेथो . के बाद से करना चाहिए नामहीन ऐप नहीं दिखाई देगा और आप डेटाबेस में नहीं देख पाएंगे।
बुनियादी घटकों का निर्माण
यह ध्यान देने योग्य है कि एमवीवीएम (मॉडल-व्यू-व्यूमॉडल) पैटर्न का उपयोग इस वास्तुकला के निर्माण के लिए आधार के रूप में किया गया था।
चलो विकास शुरू करते हैं। पहली चीज जो आपको करने की ज़रूरत है वह एक ऐसा वर्ग बनाना है जो एप्लिकेशन () को इनहेरिट करेगा। इस कक्षा में, हम ऐप के संदर्भ को इसके आगे उपयोग के लिए एक्सेस देंगे।
@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 put डालेंगे :
- त्रुटि प्रसंस्करण
- प्रदर्शित प्रगति पट्टी के साथ प्रसंस्करण लोड हो रहा है
- और, चूंकि मेरे पास सूचियों वाला एक ऐप है, जो एडॉप्टर में रसीद और डेटा उपलब्धता को प्लेसहोल्डर के रूप में संसाधित करता है जो उनकी अनुपस्थिति में प्रदर्शित होता है।
val errorLiveData = MediatorLiveData<String>()
val isLoadingLiveData = MediatorLiveData<Boolean>()
val isEmptyDataPlaceholderLiveData = MediatorLiveData<Boolean>()
फ़ंक्शन कार्यान्वयन के परिणामों को LiveData में स्थानांतरित करने के लिए मैं उपभोक्ता . का उपयोग करूंगा ।
ऐप में किसी भी स्थान पर त्रुटियों को संसाधित करने के लिए, आपको एक उपभोक्ता बनाना होगा जो 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_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 के रूप में त्रुटि . के रूप में प्रोसेसर, आपको क्रियान्वित विधि त्रुटि के बारे में संदेश दिखाई देगा।
एक ऐसी विधि बनाएं जो आपको गतिविधि में टुकड़ों को बैक स्टैक में जोड़ने की क्षमता के साथ बदलने की अनुमति दे।
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()
}
और अंत में ऑनक्रिएट . में विधि मैं दृश्य के लिए एक आईडी सेट करता हूं:
if (hasProgressBar()) {
vProgress = findViewById(progressBarId())
vProgress?.setOnClickListener(null)
}
vPlaceholder = findViewById(placeholderId())
startObserveLiveData()
मैंने मूल व्यूमोडेल और मूल गतिविधि के निर्माण की वर्तनी लिखी है। बेसिक फ्रैगमेंट उसी सिद्धांत का पालन करते हुए बनाया जाएगा।
जब आप प्रत्येक अलग स्क्रीन बनाते हैं, यदि आप और विस्तार और संभावित परिवर्तनों पर विचार कर रहे हैं, तो आपको इसके ViewModel के साथ एक अलग Fragment बनाने की आवश्यकता है।
नोट:उस स्थिति में जब फ्रैगमेंट को एक क्लस्टर में जोड़ा जा सकता है, और व्यावसायिक तर्क में भारी जटिलता नहीं होती है, तो कई फ़्रैगमेंट एक व्यूमॉडल का उपयोग कर सकते हैं।
फ्रैगमेंट के बीच स्विचिंग गतिविधि में लागू किए गए इंटरफेस के कारण होता है। ऐसा करने के लिए, प्रत्येक टुकड़े में एक सहयोगी वस्तु{ } . होनी चाहिए तर्कों की क्षमता के साथ फ्रैगमेंट ऑब्जेक्ट बिल्डिंग की विधि के साथ बंडल . में स्थानांतरित करें :
companion object {
fun newInstance() = FavoriteFragment().apply { arguments = Bundle() }
}
आर्किटेक्चर समाधान
जब बुनियादी घटक बनाए जाते हैं, तो यह वास्तुकला पर ध्यान केंद्रित करने का समय होता है। योजनाबद्ध रूप से यह प्रसिद्ध रॉबर्ट सी मार्टिन या अंकल बॉब द्वारा बनाई गई स्वच्छ वास्तुकला की तरह दिखेगा। लेकिन चूंकि मैं RxJava2 . का उपयोग करता हूं , मुझे सीमाओं . से छुटकारा मिल गया है इंटरफेस (निर्भरता नियम सुनिश्चित करने के तरीके के रूप में) निष्पादन) मानक के पक्ष में अवलोकन योग्य और सदस्य .
इसके अलावा RxJava2 . का उपयोग करके उपकरण मैंने इसके साथ अधिक लचीले काम के लिए डेटा रूपांतरण को एकीकृत किया है। यह सर्वर प्रतिक्रियाओं और डेटाबेस के साथ काम करने दोनों से संबंधित है।
प्राथमिक मॉडल के अलावा, मैं कक्ष . के लिए सर्वर प्रतिक्रिया मॉडल और अलग तालिका मॉडल बनाऊंगा . इन दो मॉडलों के बीच डेटा परिवर्तित करना, आप रूपांतरण प्रक्रिया के दौरान कोई भी परिवर्तन कर सकते हैं, सर्वर प्रतिक्रियाओं को परिवर्तित कर सकते हैं, और आवश्यक डेटा को UI पर प्रदर्शित होने से पहले आधार पर सहेज सकते हैं और इसी तरह।
टुकड़े UI . के लिए जिम्मेदार हैं , और ViewModel Fragments व्यावसायिक तर्क निष्पादन के लिए ज़िम्मेदार हैं। यदि व्यावसायिक तर्क पूरी गतिविधि से संबंधित है, तो ViewModel गतिविधि।
ViewModels वैल … आलसी द्वारा{}, . के माध्यम से एक प्रदाता से इसके आरंभीकरण द्वारा डेटा प्राप्त करते हैं यदि आपको एक अपरिवर्तनीय वस्तु की आवश्यकता है, या 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
}
इस उदाहरण में, मैं बुनियादी रूपांतरणों जैसे मॉडल-मॉडल, मॉडलों की सूची - मॉडलों की सूची और समान संयोजनों का उपयोग करता हूं लेकिन केवल सिंगलट्रांसफॉर्मर का उपयोग करता हूं डेटाबेस में सर्वर प्रतिक्रियाओं और अनुरोधों के प्रसंस्करण के लिए।
आइए नेटवर्क से शुरू करें - RestClient के साथ। रेट्रोफिटबिल्डर विधि निम्नलिखित होगी:
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) }
}
}
}
}
मॉडल बनाने के लिए, एक एपीआई पर निर्माण करना आवश्यक है। उदाहरण के तौर पर, मैं newsapi.org से गैर-व्यावसायिक उपयोग के लिए नि:शुल्क एपीयू का उपयोग करूंगा। इसमें अनुरोधित कार्यक्षमता की एक विस्तृत सूची है, लेकिन मैं इस उदाहरण के लिए एक छोटे से हिस्से का उपयोग करूंगा। एक त्वरित पंजीकरण के बाद, आपको एपीआई और आपकी एपीआई कुंजी . तक पहुंच प्राप्त होती है जो प्रत्येक अनुरोध के लिए आवश्यक है।
समापन बिंदु के रूप में, मैं https://newsapi.org/v2/everything का उपयोग करूंगा <मजबूत>। सुझाई गई क्वेरी . से मैं निम्नलिखित चुनता हूं:q - खोज क्वेरी, से - दिनांक से क्रमित करना, से - तिथि के अनुसार क्रमित करना, सॉर्टबी - चयनित मानदंड के आधार पर छँटाई, और apiKey होना चाहिए।
RestClient . के बाद निर्माण, मैं हमारे ऐप के लिए चयनित क्वेरी के साथ एक एपीआई इंटरफ़ेस बनाता हूं:
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 तत्वों को बदलने के लिए करूंगा।
जब सब कुछ अनुरोध के लिए तैयार हो जाता है, तो मैं नेटवर्क मॉडल के लिए कन्वर्टर्स बनाता हूं जिसका उपयोग हम NetworkModule के माध्यम से प्राप्त समाचारों की क्वेरी में करेंगे।
कन्वर्टर्स नेस्टिंग से उल्टे क्रम में बनाए जाते हैं, और वे तदनुसार सीधे क्रम में परिवर्तित होते हैं। तो पहला मैं आर्टिकल पर बनाता हूं, दूसरा न्यूज पर:
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 होते हैं, इसलिए आपको BaseModule, टाइप किया गया API, नेटवर्क मॉड्यूल और रूपांतरण मॉडल बनाने की आवश्यकता होती है।
यह इस तरह दिखता है:
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())
}
इस एपीआई के लिए, एपीआई कुंजी किसी भी सुझाए गए एंडपॉइंट द्वारा अनुरोध करने के लिए एक महत्वपूर्ण पैरामीटर है। इसलिए आपको यह सुनिश्चित करने की आवश्यकता है कि वैकल्पिक पैरामीटर पहले से निर्दिष्ट नहीं किए जाएंगे, और आपको उन्हें डिफ़ॉल्ट रूप से रद्द करने की आवश्यकता है।
जैसा कि आप ऊपर देख सकते हैं, मैंने प्रतिक्रिया प्रसंस्करण के दौरान डेटा रूपांतरण लागू किया।
चलो डेटाबेस के साथ काम करते हैं। मैं ऐप डेटाबेस बनाता हूं, इसे AppDatabase . कहते हैं और RoomDatabase() . से इनहेरिट करें ।
डेटाबेस इनिशियलाइज़ेशन के लिए, DatabaseCreator . बनाना आवश्यक है , जिसे ऐप्लिकेशन . में प्रारंभ किया जाना चाहिए कक्षा।
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() })
}
}
अब ऑनक्रिएट () . में ऐप . की विधि क्लास I इनिशियलाइज़ स्टेथो और डेटाबेस:
override fun onCreate() {
super.onCreate()
instance = this
Stetho.initializeWithDefaults(this)
DatabaseCreator.createDatabase(this)
}
जब डेटाबेस बनाया जाता है, तो मैं एक सिंगल इंसर्ट () मेथड के साथ एक बेसिक डाओ बनाता हूं:
@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)
}
}
बेस रिपोजिटरी विभिन्न तालिकाओं के साथ काम करने के लिए उपलब्ध है। आइए इसे लिखते हैं। यह अपने सरलतम संस्करण में निम्नलिखित जैसा दिखेगा जो कि ऐप के लिए पर्याप्त है:
abstract class BaseRepository<M, DBModel> {
protected abstract val dataConverter: BaseDataConverter<M, DBModel>
protected abstract val dao: BaseDao<DBModel>
}
BaseRepository बनाने के बाद, मैं NewsRepository create बनाऊंगा :
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
}
फिर समाचार प्रदाता निम्न जैसा दिखेगा:
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, और आगे की छँटाई लोकप्रियता . द्वारा की जाएगी उपनाम। यदि आवश्यक हो, तो आप इन मापदंडों को बदलने के लिए न्यूनतम कार्यक्षमता जोड़ सकते हैं।
चूंकि newsapi.org आपको कोई समाचार आईडी प्रदान नहीं करता है, इसलिए मैं तत्व अनुक्रमणिका को आईडी के रूप में स्वीकार करता हूं। लोकप्रियता टैग द्वारा छँटाई भी एपीआई के माध्यम से लागू की जाती है। लेकिन लोकप्रियता के आधार पर छांटने के दौरान आधार में समान आईडी के साथ डेटा पुनर्लेखन से बचने के लिए, मैं समाचार सूची लोड होने से पहले आधार में डेटा उपलब्धता को सत्यापित करूंगा। यदि आधार खाली है - नई सूची लोड हो रही है, यदि नहीं - अधिसूचना दिखाई जाती है।
आइए onViewCreated() . में कॉल करें NewsFragment . की विधि निम्नलिखित विधि:
private fun loadLikedNews() {
viewModel.loadLikedNews()
}
चूंकि हमारा आधार खाली है, विधि loadNews() लॉन्च किया जायेगा। लाइवडेटा देखें . में विधि मैं अपने लोडिंग LiveData का उपयोग करूंगा - 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?
यदि आप फिर से "पसंद करें" पर क्लिक करते हैं, तो सहेजा गया तत्व आधार से हटा दिया जाएगा।
रैपिंग अप
इस प्रकार, मैंने आपको Android ऐप विकास के लिए एक सरल और स्पष्ट दृष्टिकोण दिखाया। हमने स्वच्छ वास्तुकला के मुख्य सिद्धांतों का पालन किया लेकिन इसे यथासंभव सरल बनाया।
मैंने आपको जो आर्किटेक्चर प्रदान किया है और मिस्टर मार्टिन से क्लीन आर्किटेक्चर में क्या अंतर है? बहुत शुरुआत में, मैंने देखा है कि मेरी वास्तुकला सीए के समान है क्योंकि इसका उपयोग आधार के रूप में किया जाता है। यहां सीए योजना नीचे दी गई है:
ईवेंट प्रस्तुतकर्ता के पास जाता है, और फिर केस का उपयोग करें पर जाता है। केस का प्रयोग करें अनुरोध Repository. रिपोजिटरी को डेटा प्राप्त होता है, बनाया गया इकाई, और उसे UseCase. . में स्थानांतरित कर देता है इस प्रकार, केस का उपयोग करें सभी आवश्यक संस्थाओं को प्राप्त करता है। व्यावसायिक तर्क के कार्यान्वयन के बाद, आपको वह परिणाम मिलता है जो प्रस्तुतकर्ता, . के पास वापस आता है और यह, बदले में, परिणाम को UI. . में स्थानांतरित कर देता है
नीचे दी गई योजना में, नियंत्रक इनपुटपोर्ट . से कॉल के तरीके जो UseCase . को लागू करता है , और आउटपुटपोर्ट इंटरफ़ेस को यह प्रतिक्रिया मिलती है और प्रस्तुतकर्ता इसे लागू करता है। यूजकेस . के बजाय प्रस्तुतकर्ता, . के आधार पर प्रत्यक्ष यह इसकी परतों में इंटरफ़ेस पर निर्भर करता है, और यह निर्भरता नियम, . के विपरीत नहीं है और प्रस्तुतकर्ता को इस इंटरफ़ेस को लागू करना चाहिए।
इस प्रकार, बाहरी परत में लागू की जाने वाली प्रक्रियाएं आंतरिक परत में प्रक्रियाओं को प्रभावित नहीं करती हैं। स्वच्छ वास्तुकला में इकाई क्या है? वास्तव में, यह सब कुछ है जो किसी विशिष्ट ऐप पर निर्भर नहीं करता है, और यह कई ऐप के लिए एक सामान्य अवधारणा होगी। लेकिन मोबाइल विकास प्रक्रिया में एंटिटी ऐप की व्यावसायिक वस्तु है, जिसमें सामान्य और उच्च-स्तरीय नियम (ऐप बिजनेस लॉजिक) होते हैं।
गेटवे के बारे में क्या? जैसा कि मैंने देखा, गेटवे डेटाबेस के साथ काम करने के लिए एक भंडार है और नेटवर्क के साथ काम करने के लिए एक मॉड्यूल है। हमने नियंत्रक से छुटकारा पा लिया क्योंकि शुरू में क्लीन आर्किटेक्चर को उच्च जटिलता के व्यावसायिक ऐप की संरचना के लिए बनाया गया था, और डेटा कन्वर्टर्स मेरे ऐप में अपने कार्य करते हैं। ViewModels प्रस्तुतकर्ताओं की जगह UI प्रसंस्करण के लिए डेटा को Fragments में स्थानांतरित करता है।
मेरे दृष्टिकोण में, मैं निर्भरता नियम का सख्ती से पालन करता हूं, और रिपॉजिटरी, मॉड्यूल, मॉडल और प्रदाताओं के तर्क को समझाया गया है, और इंटरफेस के माध्यम से उन तक पहुंच संभव है। इस प्रकार, बाहरी परतों में परिवर्तन आंतरिक परतों को प्रभावित नहीं करते हैं। और कार्यान्वयन प्रक्रिया RxJava2 . का उपयोग कर रही है , कोटलिनआरएक्स , और कोटलिन लाइवडेटा डेवलपर के कार्यों को आसान, स्पष्ट बनाता है, और कोड अच्छी तरह से पढ़ा और आसानी से एक्स्टेंसिबल हो जाता है।