Android के साथ सामान्य Kotlin पैटर्न का इस्तेमाल करना

इस विषय में, Android के लिए डेवलपमेंट करते समय Kotlin लैंग्वेज के कुछ सबसे काम के पहलुओं पर फ़ोकस किया गया है.

फ़्रैगमेंट के साथ काम करना

यहां दिए गए सेक्शन में, Kotlin की कुछ बेहतरीन सुविधाओं को हाइलाइट करने के लिए Fragment उदाहरणों का इस्तेमाल किया गया है.

Inheritance

Kotlin में, class कीवर्ड का इस्तेमाल करके क्लास का एलान किया जा सकता है. यहां दिए गए उदाहरण में, LoginFragment, Fragment की सबक्लास है. सबक्लास और उसके पैरंट के बीच : ऑपरेटर का इस्तेमाल करके, इनहेरिटेंस के बारे में बताया जा सकता है:

class LoginFragment : Fragment()

इस क्लास की परिभाषा में, LoginFragment अपनी सुपरक्लास Fragment के कंस्ट्रक्टर को कॉल करने के लिए ज़िम्मेदार है.

LoginFragment में, लाइफ़साइकल के कई कॉलबैक को बदला जा सकता है, ताकि Fragment में स्थिति बदलने पर जवाब दिया जा सके. किसी फ़ंक्शन को बदलने के लिए, override कीवर्ड का इस्तेमाल करें. इसका उदाहरण यहां दिया गया है:

override fun onCreateView(
        inflater: LayoutInflater,
        container: ViewGroup?,
        savedInstanceState: Bundle?
): View? {
    return inflater.inflate(R.layout.login_fragment, container, false)
}

पैरंट क्लास में किसी फ़ंक्शन को रेफ़रंस करने के लिए, super कीवर्ड का इस्तेमाल करें. जैसा कि इस उदाहरण में दिखाया गया है:

override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
    super.onViewCreated(view, savedInstanceState)
}

शून्य होने की स्थिति और शुरुआत

पिछले उदाहरणों में, बदले गए तरीकों में मौजूद कुछ पैरामीटर के टाइप में सवाल का निशान ? जोड़ा गया है. इससे पता चलता है कि इन पैरामीटर के लिए पास किए गए आर्ग्युमेंट, शून्य हो सकते हैं. शून्य होने की स्थिति को सुरक्षित तरीके से मैनेज करें.

Kotlin में, किसी ऑब्जेक्ट के बारे में बताते समय, उसकी प्रॉपर्टी को शुरू करना ज़रूरी है. इसका मतलब है कि जब आपको किसी क्लास का इंस्टेंस मिलता है, तो उसकी ऐक्सेस की जा सकने वाली किसी भी प्रॉपर्टी को तुरंत रेफ़रंस किया जा सकता है. हालांकि, Fragment में मौजूद View ऑब्जेक्ट, Fragment#onCreateView को कॉल करने तक इन्फ़्लेट नहीं किए जा सकते. इसलिए, आपको View के लिए प्रॉपर्टी शुरू करने की प्रोसेस को कुछ समय के लिए रोकना होगा.

lateinit की मदद से, प्रॉपर्टी को शुरू करने की प्रोसेस को कुछ समय के लिए रोका जा सकता है. lateinit का इस्तेमाल करते समय, आपको अपनी प्रॉपर्टी को जल्द से जल्द शुरू करना चाहिए.

इस उदाहरण में, onViewCreated में View ऑब्जेक्ट असाइन करने के लिए lateinit का इस्तेमाल करने का तरीका दिखाया गया है:

class LoginFragment : Fragment() {

    private lateinit var usernameEditText: EditText
    private lateinit var passwordEditText: EditText
    private lateinit var loginButton: Button
    private lateinit var statusTextView: TextView

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)

        usernameEditText = view.findViewById(R.id.username_edit_text)
        passwordEditText = view.findViewById(R.id.password_edit_text)
        loginButton = view.findViewById(R.id.login_button)
        statusTextView = view.findViewById(R.id.status_text_view)
    }

    ...
}

एसएएम कन्वर्ज़न

Android में क्लिक इवेंट को सुनने के लिए, OnClickListener इंटरफ़ेस लागू करें. Button ऑब्जेक्ट में एक setOnClickListener() फ़ंक्शन होता है, जो OnClickListener के लागू होने की जानकारी लेता है.

OnClickListener में एक ऐब्स्ट्रैक्ट तरीका, onClick() होता है. इसे आपको लागू करना होगा. दरअसल, setOnClickListener() हमेशा OnClickListener को आर्ग्युमेंट के तौर पर लेता है. साथ ही, OnClickListener में हमेशा एक ही सिंगल ऐब्स्ट्रैक्ट मैथड होता है. इसलिए, इस तरीके को Kotlin में ऐनॉनमस फ़ंक्शन का इस्तेमाल करके दिखाया जा सकता है. इस प्रोसेस को सिंगल ऐब्स्ट्रैक्ट मेथड कन्वर्ज़न या एसएएम कन्वर्ज़न कहा जाता है.

एसएएम कन्वर्ज़न से, आपके कोड को काफ़ी हद तक बेहतर बनाया जा सकता है. यहां दिए गए उदाहरण में बताया गया है कि OnClickListener के लिए Button को लागू करने के लिए, एसएएम कन्वर्ज़न का इस्तेमाल कैसे किया जाता है:

loginButton.setOnClickListener {
    val authSuccessful: Boolean = viewModel.authenticate(
            usernameEditText.text.toString(),
            passwordEditText.text.toString()
    )
    if (authSuccessful) {
        // Navigate to next screen
    } else {
        statusTextView.text = requireContext().getString(R.string.auth_failed)
    }
}

setOnClickListener() को पास किए गए ऐनॉनमस फ़ंक्शन में मौजूद कोड तब काम करता है, जब कोई उपयोगकर्ता loginButton पर क्लिक करता है.

कंपैनियन ऑब्जेक्ट

कंपैनियन ऑब्जेक्ट, वैरिएबल या फ़ंक्शन को तय करने का एक तरीका है. ये वैरिएबल या फ़ंक्शन, किसी टाइप से कॉन्सेप्ट के तौर पर लिंक होते हैं, लेकिन किसी खास ऑब्जेक्ट से नहीं जुड़े होते. कंपैनियन ऑब्जेक्ट, वैरिएबल और तरीकों के लिए Java के static कीवर्ड का इस्तेमाल करने जैसा होता है.

यहां दिए गए उदाहरण में, TAG एक String कॉन्स्टेंट है. आपको LoginFragment के हर इंस्टेंस के लिए, String का एक यूनीक इंस्टेंस बनाने की ज़रूरत नहीं है. इसलिए, आपको इसे कंपैनियन ऑब्जेक्ट में तय करना चाहिए:

class LoginFragment : Fragment() {

    ...

    companion object {
        private const val TAG = "LoginFragment"
    }
}

TAG को फ़ाइल के टॉप लेवल पर तय किया जा सकता है. हालांकि, फ़ाइल में टॉप लेवल पर तय किए गए कई वैरिएबल, फ़ंक्शन, और क्लास भी हो सकते हैं. कंपैनियन ऑब्जेक्ट की मदद से, किसी क्लास के वैरिएबल, फ़ंक्शन, और क्लास की परिभाषा को कनेक्ट किया जा सकता है. इसके लिए, उस क्लास के किसी खास इंस्टेंस का इस्तेमाल नहीं किया जाता.

प्रॉपर्टी का ऐक्सेस सौंपना

प्रॉपर्टी शुरू करते समय, Android के कुछ सामान्य पैटर्न दोहराए जा सकते हैं. जैसे, Fragment में ViewModel को ऐक्सेस करना. ज़्यादा डुप्लीकेट कोड से बचने के लिए, Kotlin के प्रॉपर्टी डेलिगेशन सिंटैक्स का इस्तेमाल किया जा सकता है.

private val viewModel: LoginViewModel by viewModels()

प्रॉपर्टी डेलिगेशन, एक ऐसा सामान्य तरीका है जिसे पूरे ऐप्लिकेशन में फिर से इस्तेमाल किया जा सकता है. Android KTX, आपके लिए कुछ प्रॉपर्टी डेलिगेट उपलब्ध कराता है. viewModels, उदाहरण के लिए, ViewModel को वापस पाता है, जो मौजूदा Fragment के स्कोप में है.

प्रॉपर्टी डेलिगेशन में रिफ़्लेक्शन का इस्तेमाल किया जाता है. इससे परफ़ॉर्मेंस पर कुछ असर पड़ता है. इसका फ़ायदा यह है कि इसमें कम शब्दों में सिंटैक्स लिखा जाता है, जिससे डेवलपमेंट में लगने वाला समय बचता है.

वैल्यू के शून्य होने की जांच

Kotlin में, वैल्यू न होने की स्थिति के लिए सख्त नियम मौजूद होते हैं. ये नियम, आपके पूरे ऐप्लिकेशन में टाइप-सेफ़्टी बनाए रखते हैं. Kotlin में, ऑब्जेक्ट के रेफ़रंस में डिफ़ॉल्ट रूप से शून्य वैल्यू नहीं हो सकतीं. किसी वैरिएबल को शून्य वैल्यू असाइन करने के लिए, आपको शून्य किया जा सकने वाला वैरिएबल टाइप घोषित करना होगा. इसके लिए, बेस टाइप के आखिर में ? जोड़ें.

उदाहरण के लिए, Kotlin में इस एक्सप्रेशन का इस्तेमाल नहीं किया जा सकता. name, String टाइप का है और इसे नल नहीं किया जा सकता:

val name: String = null

शून्य वैल्यू की अनुमति देने के लिए, आपको शून्य वैल्यू स्वीकार करने वाले String टाइप, String? का इस्तेमाल करना होगा. इसका उदाहरण यहां दिया गया है:

val name: String? = null

इंटरऑपरेबिलिटी (दूसरे डिवाइस के साथ काम करना)

Kotlin के सख्त नियमों की वजह से, आपका कोड ज़्यादा सुरक्षित और छोटा होता है. इन नियमों से, NullPointerException की संभावना कम हो जाती है. इसकी वजह से, आपका ऐप्लिकेशन क्रैश हो सकता है. इसके अलावा, ये आपके कोड में नल की जांच करने की संख्या को कम करते हैं.

Android ऐप्लिकेशन लिखते समय, आपको अक्सर नॉन-Kotlin कोड को भी कॉल करना पड़ता है. ऐसा इसलिए, क्योंकि ज़्यादातर Android API, Java प्रोग्रामिंग लैंग्वेज में लिखे जाते हैं.

नल वैल्यू असाइन करने की सुविधा एक ऐसी सुविधा है जिसमें Java और Kotlin के व्यवहार में अंतर होता है. Java में, नल वैल्यू वाले सिंटैक्स के लिए कम पाबंदियां हैं.

उदाहरण के लिए, Account क्लास में कुछ प्रॉपर्टी होती हैं. इनमें String प्रॉपर्टी भी शामिल है, जिसे name कहा जाता है. Java में, Kotlin की तरह वैल्यू के शून्य होने की शर्त से जुड़े नियम नहीं हैं. इसके बजाय, इसमें शून्यता एनोटेशन का इस्तेमाल किया जाता है. हालांकि, यह ज़रूरी नहीं है. इससे यह साफ़ तौर पर बताया जाता है कि क्या शून्य वैल्यू असाइन की जा सकती है.

Android फ़्रेमवर्क मुख्य रूप से Java में लिखा गया है. इसलिए, अगर आपने nullability एनोटेशन के बिना एपीआई कॉल किए हैं, तो आपको यह समस्या आ सकती है.

प्लैटफ़ॉर्म के टाइप

अगर एनोटेशन न किए गए name मेंबर को रेफ़रंस करने के लिए Kotlin का इस्तेमाल किया जाता है और उसे Java Account क्लास में तय किया गया है, तो कंपाइलर को यह पता नहीं चलता कि Kotlin में String, String या String? से मैप होता है. इस अस्पष्टता को प्लैटफ़ॉर्म टाइप String! के ज़रिए दिखाया जाता है.

String! का Kotlin कंपाइलर के लिए कोई खास मतलब नहीं है. String!, String या String? में से किसी एक को दिखा सकता है. साथ ही, कंपाइलर आपको इनमें से किसी भी टाइप की वैल्यू असाइन करने की अनुमति देता है. ध्यान दें कि अगर आपने टाइप को String के तौर पर दिखाया है और शून्य वैल्यू असाइन की है, तो आपको NullPointerException मिल सकता है.

इस समस्या को हल करने के लिए, Java में कोड लिखते समय, शून्यता एनोटेशन का इस्तेमाल करना चाहिए. ये एनोटेशन, Java और Kotlin, दोनों तरह के डेवलपर की मदद करते हैं.

उदाहरण के लिए, यहां Java में तय की गई Account क्लास दी गई है:

public class Account implements Parcelable {
    public final String name;
    public final String type;
    private final @Nullable String accessId;

    ...
}

सदस्यता वाले वैरिएबल में से एक, accessId को @Nullable के साथ एनोटेट किया गया है. इससे पता चलता है कि इसमें शून्य वैल्यू हो सकती है. इसके बाद, Kotlin accessId को String? के तौर पर लेगा.

अगर आपको यह बताना है कि कोई वैरिएबल कभी भी शून्य नहीं हो सकता, तो @NonNull एनोटेशन का इस्तेमाल करें:

public class Account implements Parcelable {
    public final @NonNull String name;
    ...
}

इस उदाहरण में, Kotlin में name को नॉन-नलेबल String माना जाता है.

नलेबिलिटी एनोटेशन, सभी नए Android API और कई मौजूदा Android API में शामिल होते हैं. कई Java लाइब्रेरी में, Kotlin और Java डेवलपर की बेहतर मदद करने के लिए, nullability annotation जोड़े गए हैं.

शून्य वैल्यू को मैनेज करना

अगर आपको किसी Java टाइप के बारे में पक्का नहीं है, तो उसे नल वैल्यू स्वीकार करने वाला टाइप माना जाना चाहिए. उदाहरण के लिए, Account क्लास के name सदस्य को एनोटेट नहीं किया गया है. इसलिए, आपको इसे नल हो सकने वाला String? मानना चाहिए.

अगर आपको name को ट्रिम करना है, ताकि इसकी वैल्यू में शुरुआती या आखिरी सफ़ेद जगह शामिल न हो, तो Kotlin के trim फ़ंक्शन का इस्तेमाल किया जा सकता है. String? को सुरक्षित तरीके से कई तरीकों से ट्रिम किया जा सकता है. इनमें से एक तरीका, not-null assertion operator !! का इस्तेमाल करना है. इसे यहां दिए गए उदाहरण में दिखाया गया है:

val account = Account("name", "type")
val accountName = account.name!!.trim()

!! ऑपरेटर, अपनी बाईं ओर मौजूद हर चीज़ को नॉन-नल के तौर पर मानता है. इसलिए, इस मामले में name को नॉन-नल String के तौर पर माना जा रहा है. अगर बाईं ओर मौजूद एक्सप्रेशन का नतीजा शून्य है, तो आपका ऐप्लिकेशन NullPointerException दिखाता है. यह ऑपरेटर तेज़ और आसान है. हालांकि, इसका इस्तेमाल कम से कम करना चाहिए, क्योंकि इससे आपके कोड में NullPointerException के इंस्टेंस फिर से आ सकते हैं.

ज़्यादा सुरक्षित विकल्प यह है कि सुरक्षित कॉल ऑपरेटर, ?. का इस्तेमाल किया जाए. इसका उदाहरण यहां दिया गया है:

val account = Account("name", "type")
val accountName = account.name?.trim()

सेफ़-कॉल ऑपरेटर का इस्तेमाल करने पर, अगर name गैर-शून्य है, तो name?.trim() का नतीजा, नाम की ऐसी वैल्यू होती है जिसमें शुरुआत या आखिर में कोई खाली जगह नहीं होती. अगर name शून्य है, तो name?.trim() का नतीजा null होगा. इसका मतलब है कि इस स्टेटमेंट को लागू करते समय, आपका ऐप्लिकेशन कभी भी NullPointerException नहीं दिखा सकता.

सेफ़-कॉल ऑपरेटर, आपको संभावित NullPointerException से बचाता है. हालांकि, यह अगले स्टेटमेंट को शून्य वैल्यू पास करता है. हालांकि, एल्विस ऑपरेटर (?:) का इस्तेमाल करके, शून्य के मामलों को तुरंत हैंडल किया जा सकता है. इसका उदाहरण यहां दिया गया है:

val account = Account("name", "type")
val accountName = account.name?.trim() ?: "Default name"

अगर एल्विस ऑपरेटर की बाईं ओर मौजूद एक्सप्रेशन का नतीजा null है, तो दाईं ओर मौजूद वैल्यू को accountName असाइन किया जाता है. यह तकनीक, डिफ़ॉल्ट वैल्यू देने के लिए काम आती है. ऐसा न करने पर, वैल्यू शून्य हो जाएगी.

किसी फ़ंक्शन से जल्दी बाहर निकलने के लिए, Elvis ऑपरेटर का इस्तेमाल भी किया जा सकता है. इसका उदाहरण यहां दिया गया है:

fun validateAccount(account: Account?) {
    val accountName = account?.name?.trim() ?: "Default name"

    // account cannot be null beyond this point
    account ?: return

    ...
}

Android API में हुए बदलाव

Android API, Kotlin के साथ ज़्यादा आसानी से काम कर रहे हैं. Android के कई सामान्य एपीआई में, नल वैल्यू की अनुमति देने वाले एनोटेशन शामिल होते हैं. इनमें AppCompatActivity और Fragment शामिल हैं. साथ ही, Fragment#getContext जैसे कुछ कॉल के लिए, Kotlin के साथ बेहतर तरीके से काम करने वाले विकल्प उपलब्ध हैं.

उदाहरण के लिए, Fragment के Context को ऐक्सेस करने पर, लगभग हमेशा कोई वैल्यू मिलती है. ऐसा इसलिए होता है, क्योंकि Fragment में किए जाने वाले ज़्यादातर कॉल तब होते हैं, जब Fragment किसी Activity (Context का सबक्लास) से जुड़ा होता है. हालांकि, Fragment#getContext हमेशा कोई वैल्यू नहीं दिखाता, क्योंकि ऐसे मामले भी होते हैं जब Fragment किसी Activity से नहीं जुड़ा होता. इसलिए, Fragment#getContext का रिटर्न टाइप, नल हो सकता है.

Fragment#getContext से मिला Context, नल हो सकता है. साथ ही, इसे @Nullable के तौर पर एनोटेट किया गया है. इसलिए, आपको इसे अपने Kotlin कोड में Context? के तौर पर इस्तेमाल करना होगा. इसका मतलब है कि किसी ऑब्जेक्ट की प्रॉपर्टी और फ़ंक्शन ऐक्सेस करने से पहले, उस पर पहले बताए गए किसी ऑपरेटर को लागू करना. इनमें से कुछ स्थितियों के लिए, Android में ऐसे एपीआई मौजूद हैं जो इस सुविधा को उपलब्ध कराते हैं. Fragment#requireContext, उदाहरण के लिए, गैर-शून्य Context दिखाता है और अगर इसे तब कॉल किया जाता है, जब Context शून्य होगा, तो IllegalStateException दिखाता है. इस तरह, आपको Context को नॉन-नल के तौर पर इस्तेमाल करने का विकल्प मिलता है. इसके लिए, सेफ़-कॉल ऑपरेटर या वर्कअराउंड की ज़रूरत नहीं होती.

प्रॉपर्टी शुरू करना

Kotlin में प्रॉपर्टी डिफ़ॉल्ट रूप से शुरू नहीं होती हैं. इनको तब शुरू किया जाना चाहिए, जब इनकी क्लास शुरू हो.

प्रॉपर्टी को कई तरीकों से शुरू किया जा सकता है. यहां दिए गए उदाहरण में, index वैरिएबल को शुरू करने का तरीका बताया गया है. इसके लिए, क्लास डिक्लेरेशन में वैरिएबल को वैल्यू असाइन की जाती है:

class LoginFragment : Fragment() {
    val index: Int = 12
}

इस शुरुआती वैल्यू को, शुरुआती ब्लॉक में भी तय किया जा सकता है:

class LoginFragment : Fragment() {
    val index: Int

    init {
        index = 12
    }
}

ऊपर दिए गए उदाहरणों में, LoginFragment के कंस्ट्रक्ट होने पर index को शुरू किया जाता है.

हालांकि, आपके पास कुछ ऐसी प्रॉपर्टी हो सकती हैं जिन्हें ऑब्जेक्ट कंस्ट्रक्शन के दौरान शुरू नहीं किया जा सकता. उदाहरण के लिए, ऐसा हो सकता है कि आपको किसी Fragment के अंदर से किसी View को रेफ़रंस करना हो. इसका मतलब है कि लेआउट को पहले इन्फ़्लेट किया जाना चाहिए. Fragment बनाते समय, मुद्रास्फीति नहीं होती है. इसके बजाय, Fragment#onCreateView पर कॉल करने पर, यह बढ़ जाता है.

इस स्थिति को ठीक करने का एक तरीका यह है कि व्यू को नल के तौर पर घोषित किया जाए और उसे जल्द से जल्द शुरू किया जाए. जैसा कि यहां दिए गए उदाहरण में दिखाया गया है:

class LoginFragment : Fragment() {
    private var statusTextView: TextView? = null

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
            super.onViewCreated(view, savedInstanceState)

            statusTextView = view.findViewById(R.id.status_text_view)
            statusTextView?.setText(R.string.auth_failed)
    }
}

यह उम्मीद के मुताबिक काम करता है. हालांकि, अब आपको View को रेफ़रंस देते समय, इसकी नल वैल्यू को मैनेज करना होगा. इस समस्या को हल करने का बेहतर तरीका यह है कि View को शुरू करने के लिए lateinit का इस्तेमाल किया जाए. यहां इसका एक उदाहरण दिया गया है:

class LoginFragment : Fragment() {
    private lateinit var statusTextView: TextView

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
            super.onViewCreated(view, savedInstanceState)

            statusTextView = view.findViewById(R.id.status_text_view)
            statusTextView.setText(R.string.auth_failed)
    }
}

lateinit कीवर्ड की मदद से, ऑब्जेक्ट बनाते समय प्रॉपर्टी को शुरू करने से बचा जा सकता है. अगर आपकी प्रॉपर्टी को शुरू करने से पहले ही उसका रेफ़रंस दिया जाता है, तो Kotlin एक UninitializedPropertyAccessException दिखाता है. इसलिए, अपनी प्रॉपर्टी को जल्द से जल्द शुरू करें.