खंडित तर्कों के साथ आलसी बनो

May 07 2023
जब मैं कर सकता हूं तो मुझे कुछ बॉयलरप्लेट कोड हटाने में मजा आता है। कम कोड का अर्थ है कम त्रुटि-प्रवण।

जब मैं कर सकता हूं तो मुझे कुछ बॉयलरप्लेट कोड हटाने में मजा आता है। कम कोड का अर्थ है कम त्रुटि-प्रवण।

आज, मैं तर्कों को एक खंड में पारित करने और इसे सुरक्षित रूप से प्राप्त करने के बारे में बात करने जा रहा हूं।

नोट: "रचना" दुनिया इस लेख का हिस्सा नहीं है। इसके अलावा, "नेविगेशन कंपोज़" निस्संदेह अनपेक्षित तर्कों के साथ एक खामी है , एक बैकट्रैकिंग जो जावास्क्रिप्ट जैसे व्यवहार की ओर ले जाती है।

अगर मेरी तरह आप नेविगेशन कंपोनेंट का उपयोग करते हैं, जो कि एक फ़्रैगमेंट से दूसरे फ़्रैगमेंट में सुरक्षित रूप से नेविगेट करने के लिए एक बेहतरीन टूल है, तो ऐसा हो सकता है कि आपको किसी फ़्रैगमेंट को कुछ कारणों से खुद ही इंस्टेंट करना पड़े। उदाहरण के लिए, आप एक से अधिक फ़्रैगमेंट को तुरंत चालू करने के लिए FragmentStateAdapter के साथ ViewPager(2) का उपयोग कर सकते हैं। इस मामले में, नेविगेशन घटक से नवग्राफ व्यर्थ है। इसका प्रबंधन करना उस पुस्तकालय की जिम्मेदारी नहीं है।

नेविगेशन कंपोनेंट बहुत सारे एक्सटेंशन के साथ आता है जैसे "by navArgs ()" जो हमें टाइप किए गए आलसी सुरक्षित तरीके से खंड से तर्क प्राप्त करने की अनुमति देता है।

इस लेख के अंत में, कोटलिन की शक्ति के साथ, हमारे पास अपने स्वयं के टुकड़े से टाइप किए गए तर्कों को प्राप्त करने के लिए "आर्ग्स ()" द्वारा एक सुरक्षा आलसी विस्तार फ़ंक्शन होगा।

लेकिन पहले, पुराने तरीके पर एक नजर डालते हैं। इस लेख में, हम अपने कोड को सुरक्षित बनाने के लिए उत्तरोत्तर सुधार करेंगे।

बंडल

कोई आश्चर्य नहीं, डेटा पास करना प्रतिबंधित है, आपको कुछ डेटा को बंडल में पास करना होगा। किसी फ़्रैगमेंट को डेटा पास करने का कोई दूसरा तरीका नहीं है।

आइए इस पर एक त्वरित नज़र डालें, यहाँ पैकेज androidx.core.os से बंडलऑफ फ़ंक्शन है

public fun bundleOf(vararg pairs: Pair<String, Any?>): Bundle = Bundle(pairs.size).apply {
    for ((key, value) in pairs) {
        when (value) {
            null -> putString(key, null) // Any nullable type will suffice.

            // Scalars
            is Boolean -> putBoolean(key, value)
            is Byte -> putByte(key, value)
            is Char -> putChar(key, value)
            is Double -> putDouble(key, value)
            is Float -> putFloat(key, value)
            is Int -> putInt(key, value)
            is Long -> putLong(key, value)
            is Short -> putShort(key, value)

            // References
            is Bundle -> putBundle(key, value)
            is CharSequence -> putCharSequence(key, value)
            is Parcelable -> putParcelable(key, value)

            // Scalar arrays
            is BooleanArray -> putBooleanArray(key, value)
            is ByteArray -> putByteArray(key, value)
            is CharArray -> putCharArray(key, value)
            is DoubleArray -> putDoubleArray(key, value)
            is FloatArray -> putFloatArray(key, value)
            is IntArray -> putIntArray(key, value)
            is LongArray -> putLongArray(key, value)
            is ShortArray -> putShortArray(key, value)

            // Reference arrays
            is Array<*> -> {
                val componentType = value::class.java.componentType!!
                @Suppress("UNCHECKED_CAST") // Checked by reflection.
                when {
                    Parcelable::class.java.isAssignableFrom(componentType) -> {
                        putParcelableArray(key, value as Array<Parcelable>)
                    }
                    String::class.java.isAssignableFrom(componentType) -> {
                        putStringArray(key, value as Array<String>)
                    }
                    CharSequence::class.java.isAssignableFrom(componentType) -> {
                        putCharSequenceArray(key, value as Array<CharSequence>)
                    }
                    Serializable::class.java.isAssignableFrom(componentType) -> {
                        putSerializable(key, value)
                    }
                    else -> {
                        val valueType = componentType.canonicalName
                        throw IllegalArgumentException(
                            "Illegal value array type $valueType for key \"$key\""
                        )
                    }
                }
            }

            // Last resort. Also we must check this after Array<*> as all arrays are serializable.
            is Serializable -> putSerializable(key, value)

            else -> {
                if (Build.VERSION.SDK_INT >= 18 && value is IBinder) {
                    BundleApi18ImplKt.putBinder(this, key, value)
                } else if (Build.VERSION.SDK_INT >= 21 && value is Size) {
                    BundleApi21ImplKt.putSize(this, key, value)
                } else if (Build.VERSION.SDK_INT >= 21 && value is SizeF) {
                    BundleApi21ImplKt.putSizeF(this, key, value)
                } else {
                    val valueType = value.javaClass.canonicalName
                    throw IllegalArgumentException("Illegal value type $valueType for key \"$key\"")
                }
            }
        }
    }
}

class MyFragment : Fragment() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        val contentId: Long = requireArguments().getLong(BUNDLE_ARGUMENT_KEY_CONTENT_ID)
        val otherData: String = requireArguments().getString(BUNDLE_ARGUMENT_KEY_OTHER_DATA)!!
    }

    companion object {
        private const val BUNDLE_ARGUMENT_KEY_CONTENT_ID = "content_id"
        private const val BUNDLE_ARGUMENT_KEY_OTHER_DATA = "other_data"
        fun newInstance(
            contentId: Long,
            otherData: String
        ): MyFragment {
            return MyFragment().apply {
                arguments = Bundle().apply {
                    putLong(BUNDLE_ARGUMENT_KEY_CONTENT_ID, contentId)
                    putString(BUNDLE_ARGUMENT_KEY_OTHER_DATA, otherData)
                }
            }
        }

        // Or
        fun newInstance(
            contentId: Long,
            otherData: String
        ): MyFragment {
            return MyFragment().apply {
                arguments = bundleOf(
                    BUNDLE_ARGUMENT_KEY_CONTENT_ID to contentId, 
                    BUNDLE_ARGUMENT_KEY_OTHER_DATA to otherData
                )
            }
        }
    }
}

1°) हर बार जब हम एक नया तर्क जोड़ना चाहते हैं, तो हमारे पास नया पाने और सेट करने के लिए बहुत सारे बदलाव होते हैं।

2°) जावा प्रतिबंध के कारण जावा दुनिया में आदिम (स्ट्रिंग के अलावा) एक बंडल में अशक्त नहीं हो सकते।

3°) आदत से, हम प्रविष्टियों को कॉपी-पेस्ट कर सकते हैं और जोड़े गए नए तर्क के लिए सही बंडल कुंजी असाइन करना भूल जाते हैं।

4 °) newInstance फ़ंक्शन में, हम एक पैरामीटर को अशक्त से अशक्त में बदलने के लिए एक दिन तय कर सकते हैं। इस मामले में, आईडीई/संकलन-समय पर कोई त्रुटि नहीं की जाएगी। यदि हम एक अशक्त वस्तु पर एक गैर अशक्त प्राप्त करने का प्रयास करते हैं, तो संभावित रूप से एक अशक्त सूचक अपवाद के परिणामस्वरूप रनटाइम होता है।

इस पुराने तरीके पर निष्कर्ष :

मैंने इस तरह का कोड बहुत पहले बनाया था। यह कोड त्रुटि-प्रवण है, तर्कों को जोड़ने/हटाने/बदलते समय मूर्खतापूर्ण गलतियों से बचने के लिए बहुत सी बातों को ध्यान में रखना है। आजकल, एक अच्छे डेवलपर के रूप में और कोटलिन की शक्ति से हम पहले से अधिक सुरक्षित होना चाहते हैं, कम कोड लिखना और आलसी होना चाहते हैं।

कैप्सूलीकरण

ठीक है, खराब प्रदर्शन के कारण कई प्राइमेटिव को सीधे एक बंडल में खंडित तर्कों में भेजना शायद एक अच्छा विकल्प था। वस्तुओं के निर्माण की लागत होती है।

आदिम / वस्तुओं को एक अद्वितीय तर्क वस्तु में समाहित करने का एक प्रमुख लाभ है: "एक मॉडल अनुबंध रखें" और "अशक्त आदिम" की अनुमति देता है।

नोट: नेविगेशन घटक हमें xml फ़ाइलों में एक खंड के लिए अलग-अलग तर्क लिखने की अनुमति देता है। यह सुरक्षित है क्योंकि वे निर्माण के समय एक प्रत्यय "नेविगेशनआर्ग्स" के साथ एक ऑटो-जनरेटेड क्लास उत्पन्न करते हैं जो इसमें एक्सएमएल तर्कों को स्थानांतरित करता है। "नवआर्ग्स द्वारा" के साथ, हमें यह ऑटो-जनरेटेड क्लास मिल रही है।

@Parcelize
data class MyFragmentArgs(
    val contentId: Long,
    val otherData: String
) : Parcelable

class MyFragment : Fragment() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        val args: MyFragmentArgs = requireArguments().getParcelable(BUNDLE_ARGUMENT_KEY)!!
        val (contentId: Long, otherData: String) = args
    }

    companion object {
        private const val BUNDLE_ARGUMENT_KEY = "argument"
        fun newInstance(
            contentId: Long,
            otherData: String
        ): MyFragment {
            return MyFragment().apply {
                arguments = Bundle().apply {
                    putParcelable(BUNDLE_ARGUMENT_KEY, MyFragmentArgs(contentId = contentId, otherData = otherData))
                }
            }
        }
    }
}

काम करने के लिए, मॉडल को पार्सलेबल क्लास का विस्तार करने की आवश्यकता होती है और "@Parcelize" एनोटेशन के साथ, यह बॉयलरप्लेट कोड से बचता है।

इसके अलावा, अगर हमारे MyFragmentArgs मॉडल में हम "वैल अन्य डेटा: स्ट्रिंग" को "वैल अन्य डेटा: स्ट्रिंग?"

पुनर्प्रयोग

यह अच्छा हो सकता है अगर हम डुप्लिकेशन से बचने के लिए एक ही सेट रख सकें/तर्कों को किसी भी खंड में अनुबंध प्राप्त कर सकें।

चलो यह करते हैं

@Parcelize
data class MyFragmentArgs(
    val contentId: Long,
    val otherData: String
) : Parcelable

class MyFragment : Fragment() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        val args: MyFragmentArgs = args()
        val (contentId: Long, otherData: String) = args
    }

    companion object {
        private fun newInstance(
            contentId: Long,
            otherData: String
        ): MyFragment {
            return MyFragment().setArgs(MyFragmentArgs(contentId = contentId, otherData = otherData))
        }
    }
}

private const val BUNDLE_ARGUMENTS_KEY = "arguments"

fun <FRAGMENT : Fragment> FRAGMENT.setArgs(args: Parcelable): FRAGMENT {
    return apply {
        arguments = Bundle().apply {
            putParcelable(BUNDLE_ARGUMENTS_KEY, args)
        }
    }
}

fun <FRAGMENT : Fragment, ARGS : Parcelable> FRAGMENT.args(): ARGS {
    return requireArguments().getParcelable(BUNDLE_ARGUMENTS_KEY)!!
}

सुरक्षा

यह अच्छा हो सकता है अगर हम सही वर्ग को गलत तरीके से टाइप करने से बचने के लिए सेटर और गेटर को खंडित तर्कों से जोड़ सकें।

चलो यह करते हैं !

@Parcelize
data class MyFragmentArgs(
    val contentId: Long,
    val otherData: String
) : IArgs

class MyFragment : Fragment(),
                   IFragmentArgs<MyFragmentArgs> {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        val (contentId: Long, otherData: String) = args()
    }

    companion object {
        private fun newInstance(
            contentId: Long,
            otherData: String
        ): MyFragment {
            return MyFragment().setArgs(MyFragmentArgs(contentId = contentId, otherData = otherData))
        }
    }
}

interface IArgs : Parcelable
interface IFragmentArgs<ARGS : IArgs>

private const val BUNDLE_ARGUMENTS_KEY = "arguments"

fun <FRAGMENT, ARGS : IArgs> FRAGMENT.setArgs(args: ARGS): FRAGMENT where FRAGMENT : Fragment, FRAGMENT : IFragmentArgs<ARGS> {
    return apply {
        arguments = Bundle().apply {
            putParcelable(BUNDLE_ARGUMENTS_KEY, args)
        }
    }
}

fun <FRAGMENT, ARGS : IArgs> FRAGMENT.args(): ARGS where FRAGMENT : Fragment, FRAGMENT : IFragmentArgs<ARGS> {
    return requireArguments().getParcelable(BUNDLE_ARGUMENTS_KEY)!!
}

1°) तर्क मॉडल को IArgs इंटरफ़ेस का विस्तार करना चाहिए

2°) फ़्रैगमेंट को IFragmentArgs इंटरफ़ेस का विस्तार करना चाहिए

आलसी रहें

मैं "नेविगेशन कंपोनेंट" से "नेविअर्ग्स ()" पर वापस आता हूं।
यह उपयोगी क्यों है?

नोट: पहली बार सेट करने के बाद हमें तर्क नहीं बदलना चाहिए। इसलिए, तर्कों को वापस लेते समय, हम ऑप्टिमाइज़ेशन द्वारा इसे स्टोर कर सकते हैं, जब हमें पहली बार इसकी आवश्यकता होती है। यह उसके कैश सिस्टम के साथ "आलसी" वस्तु का उद्देश्य है।

अब, मिशन हमारा अपना "आर्ग्स द्वारा ()" होना है!

@Parcelize
data class MyFragmentArgs(
    val contentId: Long,
    val otherData: String
) : IArgs

class MyFragment : Fragment(),
                   IFragmentArgs<MyFragmentArgs> {

    val args by args()

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        val (contentId: Long, otherData: String) = args
    }

    companion object {
        private fun newInstance(
            contentId: Long,
            otherData: String
        ): MyFragment {
            return MyFragment().setArgs(MyFragmentArgs(contentId = contentId, otherData = otherData))
        }
    }
}

interface IArgs : Parcelable
interface IFragmentArgs<ARGS : IArgs>

private const val BUNDLE_ARGUMENTS_KEY = "argument"

fun <FRAGMENT, ARGS : IArgs> FRAGMENT.setArgs(args: ARGS): FRAGMENT where FRAGMENT : Fragment, FRAGMENT : IFragmentArgs<ARGS> {
    return apply {
        arguments = Bundle().apply {
            putParcelable(BUNDLE_ARGUMENTS_KEY, args)
        }
    }
}

@MainThread
inline fun <FRAGMENT, reified ARGS : IArgs> FRAGMENT.args(): ArgsLazy<ARGS> where FRAGMENT : Fragment, FRAGMENT : IFragmentArgs<ARGS> = ArgsLazy {
    arguments ?: throw IllegalStateException("Fragment $this has null arguments")
}

class ArgsLazy<ARGS : IArgs>(
    private val argumentsProducer: () -> Bundle
) : Lazy<ARGS> {
    private var cached: ARGS? = null

    override val value: ARGS
        get() {
            var args = cached
            if (args == null) {
                val arguments = argumentsProducer()

                args = arguments.getParcelable(BUNDLE_ARGUMENTS_KEY)!!
                cached = args
            }
            return args
        }

    override fun isInitialized(): Boolean = cached != null
}

MyFragmentArgs2 में MyFragmentArgs को डुप्लिकेट करें और नए मॉडल के साथ setArgs() को कॉल करने का प्रयास करें। एंड्रॉइड स्टूडियो आपको उस गलती के बारे में चेतावनी देगा जो आप करने जा रहे हैं। इस अनुबंध का उल्लंघन नहीं किया जा सकता है

नोट: "आर्ग्स ()" फ़ंक्शन में "@MainThread" एनोटेशन डेडलॉक के कारण निर्दिष्ट है। यह इसे एकाधिक पृष्ठभूमि थ्रेड से एक्सेस करने से रोकता है। इसे केवल मेन थ्रेड से एक्सेस किया जाना चाहिए।

बोनस - ViewModel से खंडित तर्क

class MyFragmentViewModel(override val savedStateHandle: SavedStateHandle) : ViewModel(),
                                                                             IViewModelArgs<MyFragmentArgs> {
    val args = args()

    init {
        val (contentId: Long, otherData: String) = args
    }
}

interface IViewModelArgs<ARGS : IArgs> {
    val savedStateHandle: SavedStateHandle
}


fun <VIEWMODEL, ARGS : IArgs> VIEWMODEL.args(): ARGS where VIEWMODEL : ViewModel, VIEWMODEL : IViewModelArgs<ARGS> {
    return savedStateHandle.get(BUNDLE_ARGUMENTS_KEY) as? ARGS ?: throw IllegalStateException("ViewModel $this has null arguments")
}

@Parcelize
data class MyFragmentArgs(
    val contentId: Long,
    val otherData: String
) : IArgs

class MyFragment : Fragment(),
                   IFragmentArgs<MyFragmentArgs> {

    val args by args()

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        val (contentId: Long, otherData: String) = args
    }

    companion object {
        private fun newInstance(
            contentId: Long,
            otherData: String
        ): MyFragment {
            return MyFragment().setArgs(MyFragmentArgs(contentId = contentId, otherData = otherData))
        }
    }
}

class MyFragmentViewModel(override val savedStateHandle: SavedStateHandle) : ViewModel(),
                                                                             IViewModelArgs<MyFragmentArgs> {
    val args = args()

    init {
        val (contentId: Long, otherData: String) = args
    }
}

interface IArgs : Parcelable
interface IFragmentArgs<ARGS : IArgs>
interface IViewModelArgs<ARGS : IArgs> {
    val savedStateHandle: SavedStateHandle
}

private const val BUNDLE_ARGUMENTS_KEY = "arguments"

fun <FRAGMENT, ARGS : IArgs> FRAGMENT.setArgs(args: ARGS): FRAGMENT where FRAGMENT : Fragment, FRAGMENT : IFragmentArgs<ARGS> {
    return apply {
        arguments = Bundle().apply {
            putParcelable(BUNDLE_ARGUMENTS_KEY, args)
        }
    }
}

@MainThread
inline fun <FRAGMENT, reified ARGS : IArgs> FRAGMENT.args(): ArgsLazy<ARGS> where FRAGMENT : Fragment, FRAGMENT : IFragmentArgs<ARGS> = ArgsLazy {
    arguments ?: throw IllegalStateException("Fragment $this has null arguments")
}

class ArgsLazy<ARGS : IArgs>(
    private val argumentsProducer: () -> Bundle
) : Lazy<ARGS> {
    private var cached: ARGS? = null

    override val value: ARGS
        get() {
            var args = cached
            if (args == null) {
                val arguments = argumentsProducer()

                args = arguments.getParcelable(BUNDLE_ARGUMENTS_KEY)!!
                cached = args
            }
            return args
        }

    override fun isInitialized(): Boolean = cached != null
}

fun <VIEWMODEL, ARGS : IArgs> VIEWMODEL.args(): ARGS where VIEWMODEL : ViewModel, VIEWMODEL : IViewModelArgs<ARGS> {
    return savedStateHandle.get(BUNDLE_ARGUMENTS_KEY) as? ARGS ?: throw IllegalStateException("ViewModel $this has null arguments")
}

सुरक्षित कोड लिखने के लिए कोटलिन की शक्ति का उपयोग करें और जब आप कर सकते हैं तो कम कोड लिखें: डी

यह मेरा दूसरा लेख है। मुझे उपयोगी टिप्स लिखना पसंद है।
यदि आप इस तरह के लेख को पसंद करते हैं, तो क्लैप करने में संकोच न करें