सामग्री पर जाएं
सभी पोस्ट

Android पर नोटिफ़िकेशन परमिशन कब माँगें (और Deny से कैसे उबरें)

Android के POST_NOTIFICATIONS रनटाइम परमिशन की एक व्यावहारिक गाइड — कब पूछना चाहिए, रिक्वेस्ट को कैसे प्राइम करें, और जब कोई इसे डिनाई करे तो क्या करें।

MFKAPPS 6 मिनट पढ़ना

Android 13 के बाद से, नोटिफ़िकेशन डिफ़ॉल्ट रूप से नहीं मिलते — यूज़र को POST_NOTIFICATIONS को साफ़ तौर पर हाँ कहना पड़ता है, बिल्कुल उसी तरह जैसे वह कैमरा या लोकेशन एक्सेस को हाँ कहेगा। ऐसी ऐप के लिए जहाँ नोटिफ़िकेशन पूरी वैल्यू उठाते हैं — एक हाइड्रेशन रिमाइंडर, एक दवा का अलर्ट — वह एक सिस्टम डायलॉग ही तय करता है कि ऐप काम कर रही है या सिर्फ़ एक आइकन बनकर रह गई है जो कभी-कभार खुलती है। टाइमिंग ग़लत हो जाए तो ज़्यादातर लोग “Don’t allow” पर रिफ्लेक्स से टैप कर देते हैं, इससे पहले कि उन्हें हाँ कहने की कोई वजह नज़र आए। टाइमिंग सही हो जाए तो वही डायलॉग एक सामान्य-सी बात बन जाता है। परमिशन खुद नहीं बदलती; उसके इर्द-गिर्द की रिक्वेस्ट बदलती है।

डायलॉग सिर्फ़ एक बार चलता है

POST_NOTIFICATIONS हर दूसरे Android रनटाइम परमिशन जैसा ही व्यवहार करता है: सिस्टम हर इंस्टॉल पर एक बार अपना डायलॉग दिखाता है। अगर यूज़र इसे डिनाई कर देता है, तो अगली requestPermissionLauncher.launch(Manifest.permission.POST_NOTIFICATIONS) कॉल चुपचाप false लौटाती है — न कोई डायलॉग, न कोई एरर, बस तुरंत एक डिनायल। यही वह हिस्सा है जो टेस्टिंग के दौरान लोगों को उलझा देता है: एक दूसरी ActivityResultLauncher रिक्वेस्ट ऐसी दिखती है जैसे वह कुछ नहीं कर रही, जबकि असल में वह बिल्कुल वही कर रही होती है जो उसे करना चाहिए।

val requestPermissionLauncher = registerForActivityResult(
    ActivityResultContracts.RequestPermission()
) { granted ->
    if (granted) {
        scheduleDailyReminder()
    } else {
        showNotificationsDisabledState()
    }
}

चूँकि सिस्टम डायलॉग के लिए सिर्फ़ एक ही असली मौक़ा होता है, इसलिए यह ज़्यादा मायने रखता है कि आप launch() को कब कॉल करते हैं, बजाय इसके कि कैसे कॉल करते हैं।

पहले लॉन्च पर मत पूछिए

रिफ्लेक्स यह होता है कि ऑनबोर्डिंग के दौरान ऐप को कभी भी ज़रूरत पड़ने वाली हर परमिशन माँग ली जाए, ताकि बाक़ी ऐप यह मान सके कि सब कुछ पहले से ही मिल चुका है। ख़ासकर नोटिफ़िकेशन के लिए, यह लगभग सबसे बुरी संभव टाइमिंग है। पहले लॉन्च पर यूज़र के पास यह समझने का कोई संदर्भ नहीं होता कि एक रिमाइंडर ऐप बाद में उसे क्यों टोकना चाहेगी — उसने तो आइकन को अभी बस दस सेकंड देखा है। बिना किसी पहले वाली वजह के परमिशन डायलॉग फ़्रिक्शन जैसा लगता है, और ठीक उसी पल पर फ़्रिक्शन एक सोचे-समझे “Allow” के बजाय एक रिफ्लेक्सिव “Don’t allow” ज़्यादा बार पाता है।

समाधान यह है कि उस बिंदु पर पूछा जाए जहाँ परमिशन की वैल्यू खुद-ब-खुद स्पष्ट हो। Hydrame में, यह वह पल है जब कोई अपना डेली वॉटर गोल सेट करना ख़त्म करता है — उसने अभी-अभी ऐप को बताया है कि वह कब ज़्यादा पानी पीना चाहता है, तो एक रिमाइंडर बाधा नहीं है, बल्कि वही फ़ीचर है जो उसने माँगा था। OldSchool में, यह पहली दवा और उसका शेड्यूल जोड़ने के तुरंत बाद होता है। रिक्वेस्ट एक ऐसे एक्शन के बाद आती है जो यह संकेत देता है कि यूज़र पहले से ही वह चाहता है जो परमिशन सक्षम बनाती है; सिस्टम डायलॉग बस इसे औपचारिक रूप देता है।

एक प्राइमिंग स्क्रीन एक नंगे सिस्टम डायलॉग से बेहतर होती है

सिस्टम परमिशन डायलॉग डिज़ाइन के हिसाब से संक्षिप्त होता है — एक टाइटल, एक ऐप नाम, Allow और Don’t allow। यह क्यों नहीं समझा सकता, और Android आपको इसकी कॉपी कस्टमाइज़ करने का कोई तरीक़ा नहीं देता। जो आप कंट्रोल कर सकते हैं वह है इससे ठीक पहले दिखाई जाने वाली स्क्रीन: संदर्भ का एक अकेला वाक्य, जो ऐप की ज़रूरत के बजाय यूज़र के अपने लक्ष्य के इर्द-गिर्द फ़्रेम किया गया हो।

"मैं तुम्हें उस शेड्यूल पर पानी पीने की याद दिलाऊँगा जो तुमने अभी सेट किया है।
 इन रिमाइंडर को दिखाने के लिए Android को तुम्हारी परमिशन चाहिए।"

 [ रिमाइंडर चालू करें ]   [ अभी नहीं ]

इसे कभी-कभी “प्री-परमिशन” या “सॉफ़्ट-आस्क” स्क्रीन कहा जाता है, और इसकी वैल्यू सिर्फ़ दिखावटी नहीं है — यह बदल देती है कि थोड़ी देर बाद जब सिस्टम डायलॉग दिखता है तो उसका मतलब क्या होता है। इसके बिना, OS डायलॉग यूज़र को मिलने वाली पहली और इकलौती व्याख्या होती है। इसके साथ, OS डायलॉग सिर्फ़ उस चीज़ की पुष्टि होती है जिसके लिए यूज़र पहले ही राज़ी हो चुका होता है। प्राइमिंग स्क्रीन पर “अभी नहीं” की भी कोई क़ीमत नहीं होती: यह असली परमिशन को छूती ही नहीं, इसलिए सिस्टम की वह एक कोशिश बाद में भी उपलब्ध रहती है, जब यूज़र ऐप में और समय बिता चुका होगा और उसके पास इस माँग पर भरोसा करने की और वजह होगी।

डिनायल आख़िरी नहीं होता — shouldShowRequestPermissionRationale

जब कोई सिस्टम डायलॉग को डिनाई कर देता है, तो ActivityCompat.shouldShowRequestPermissionRationale() आपको बताता है कि क्या अगली रिक्वेस्ट से पहले अपना ख़ुद का कारण फिर से दिखाना उचित है, या क्या यूज़र ने इसे स्थायी रूप से डिनाई कर दिया है (उसने “Don’t ask again” चेक किया, या Android के नए, सख़्त री-आस्क नियमों के तहत पहले ही एक बार डिनाई कर चुका है)।

fun onReminderCardTapped(activity: Activity) {
    val granted = ContextCompat.checkSelfPermission(
        activity, Manifest.permission.POST_NOTIFICATIONS
    ) == PackageManager.PERMISSION_GRANTED

    when {
        granted -> scheduleDailyReminder()
        ActivityCompat.shouldShowRequestPermissionRationale(
            activity, Manifest.permission.POST_NOTIFICATIONS
        ) -> showRationaleThenRequest()
        else -> openAppNotificationSettings(activity)
    }
}

जब यह पहले हुए डिनायल के बाद false लौटाता है, तो सिस्टम अपना डायलॉग फिर कभी नहीं दिखाएगा चाहे आप launch() को कैसे भी कॉल करें — वापस जाने का इकलौता रास्ता ऐप का नोटिफ़िकेशन सेटिंग्स पेज है, जिस तक आपके पैकेज पर स्कोप किए गए Intent(Settings.ACTION_APP_NOTIFICATION_SETTINGS) से पहुँचा जाता है। इसे “अपनी सेटिंग्स चेक करें” जैसे किसी सामान्य मैसेज के पीछे मत छिपाइए; यह intent यूज़र को सीधे सही स्क्रीन पर ले जाता है, कोई नेविगेट करने की ज़रूरत नहीं।

“नहीं” को एक असली स्टेट के रूप में डिज़ाइन करें, बग के रूप में नहीं

कुछ यूज़र इसे डिनाई करेंगे और सचमुच ऐसा ही चाहेंगे — ऐप को इस तरह बनाइए कि यह एक सपोर्टेड स्टेट हो, टूटी हुई स्टेट नहीं। Hydrame रिमाइंडर के बिना भी पानी लॉग करता रहता है; बस स्ट्रीक और हिस्ट्री को कोई नज हासिल नहीं होती। OldSchool ऐप के अंदर की दवा की लिस्ट को पूरी तरह इस्तेमाल करने लायक रखता है, और यह दिखावा करने के बजाय कि फ़ीचर मौजूद है जबकि वह चल ही नहीं सकता, एक स्थायी, डिस्मिस किए जा सकने वाला बैनर दिखाता है — “रिमाइंडर बंद हैं”। एक साइलेंट फ़ेल्यर, जहाँ एक रिमाइंडर शेड्यूल तो हुआ लेकिन कभी दिखा ही नहीं क्योंकि परमिशन कभी मिली ही नहीं, एक ईमानदार मिसिंग फ़ीचर से भी बदतर है: यह ऐसा लगता है जैसे ऐप अविश्वसनीय है, न कि यह कि यूज़र ने ख़ुद एक चुनाव किया था।

वह एक सिस्टम डायलॉग इस तैयारी के लायक़ है

POST_NOTIFICATIONS कोई मुश्किल API नहीं है — registerForActivityResult और एक अकेली परमिशन स्ट्रिंग कुछ ही लाइनों में मैकेनिक्स को कवर कर लेती हैं। जो चीज़ असल में ग्रांट रेट तय करती है, वह उन लाइनों के इर्द-गिर्द की हर चीज़ है: उस पल के बाद पूछना जब वैल्यू साफ़ हो चुकी हो, सादी भाषा में एक वाक्य के संदर्भ से प्राइम करना, और यह मानने के बजाय कि डिनायल होगा ही नहीं, उसके लिए एक असली जवाब रखना। किसी भी ऐप के लिए जहाँ रिमाइंडर ही प्रोडक्ट है, यह कोई पॉलिश नहीं है — यह वह चीज़ है जिससे फ़ीचर सिरे से काम करता है।

// संबंधित पठन

जर्नल से और भी

MFKAPPS 7 मिनट पढ़ना

2026 में Android SplashScreen API: बिना सफ़ेद फ़्लैश के कोल्ड स्टार्ट

Android के SplashScreen API की एक व्यावहारिक गाइड — थीम सेटअप, एनिमेटेड आइकन की साइज़ सीमाएँ जिन्हें कोई नहीं पढ़ता, और अभी तैयार न हुए डेटा के लिए keepOnScreenCondition।

#android #engineering #kotlin
MFKAPPS 6 मिनट पढ़ना

Jetpack Compose में Material You डायनामिक कलर: जब वॉलपेपर जीत जाए तो अपना ब्रांड कलर बचाए रखना

dynamicColorScheme() आपके पैलेट को यूज़र के वॉलपेपर से बने पैलेट से बदल देता है। ब्रांड रंगों को खोने के बजाय उन्हें हार्मोनाइज़ करने की एक व्यावहारिक गाइड।

#android #engineering #kotlin
MFKAPPS 5 मिनट पढ़ना

Android का In-App Review API: बिना परेशान किए रेटिंग माँगना

Google के In-App Review API की एक व्यावहारिक गाइड — यह असल में कैसे काम करता है, इसे कब ट्रिगर करना चाहिए, और सामान्य 'हमें रेट करें' पॉपअप आपकी Play Store रेटिंग को चुपचाप कैसे नुकसान पहुँचा रहा है।

#android #engineering #kotlin