2026 में Android का इन-ऐप अपडेट API: फ़्लेक्सिबल बनाम इमीडिएट, और कब किसे इस्तेमाल करें
Google के इन-ऐप अपडेट API की व्यावहारिक गाइड — फ़्लेक्सिबल बनाम इमीडिएट फ़्लो, अपडेट प्रायोरिटी, स्टेलनेस डेज़, और वह रिज़्यूम चेक जिसे हर कोई भूल जाता है।
Play Store का ऑटो-अपडेट देर-सवेर आपके अधिकांश यूज़र्स तक पहुंच जाता है, लेकिन उस वाक्य में “देर-सवेर” बहुत कुछ छुपा रहा है। सिर्फ़-Wi-Fi सेटिंग, स्टोरेज का दबाव, या ऐसा यूज़र जो Play Store ऐप कभी खोलता ही नहीं — ये किसी को तीन वर्ज़न पीछे छोड़ सकते हैं, अब भी उसी बिल्ड पर जिसका बग आपने दो हफ्ते पहले ठीक किया था, या इससे भी बुरा, अब भी उसी सर्वर कॉन्ट्रैक्ट से टकरा रहा है जिसे आपका बैकएंड अब सपोर्ट ही नहीं करता। Google का इन-ऐप अपडेट API ठीक इसी गैप के लिए है: यह आपके ऐप को यह जांचने देता है कि कोई नया वर्ज़न मौजूद है या नहीं, और यूज़र को कहीं और भेजे बिना, ऐप के अंदर से ही अपडेट के लिए पूछ लेता है।
इस API में दो अलग-अलग फ़्लो हैं, और गलत वाला चुनना इसे बुरी तरह शिप करने का सबसे आम तरीका है।
यह असल में कैसे काम करता है
यह API Play Core (com.google.android.play:app-update-ktx) का हिस्सा है। आप AppUpdateManager से मौजूदा AppUpdateInfo मांगते हैं, जो बताता है कि कोई अपडेट उपलब्ध है या नहीं और Google को उसके बारे में क्या पता है:
val updateManager = AppUpdateManagerFactory.create(context)
updateManager.appUpdateInfo.addOnSuccessListener { info ->
if (info.updateAvailability() == UpdateAvailability.UPDATE_AVAILABLE) {
// decide which flow to start based on info.updatePriority()
// and info.clientVersionStalenessDays()
}
}
यह AppUpdateInfo ऑब्जेक्ट लगभग पूरा API है — बाकी सब यह तय करने की बात है कि इसके साथ क्या करना है।
फ़्लेक्सिबल बनाम इमीडिएट: दो अलग काम
फ़्लेक्सिबल बैकग्राउंड में अपडेट डाउनलोड करता है जबकि यूज़र ऐप इस्तेमाल करता रहता है, फिर तैयार होने पर एक छोटा “रीस्टार्ट करके अपडेट करें” स्नैकबार दिखाता है। यूज़र ब्लॉक नहीं होता और प्रॉम्प्ट को नज़रअंदाज़ कर सकता है। जो भी अत्यावश्यक नहीं है उसके लिए यही सही डिफ़ॉल्ट है — एक UI पॉलिश, एक नया फ़ीचर, एक ऐसा बग फिक्स जो कुछ तोड़ता नहीं।
if (info.isUpdateTypeAllowed(AppUpdateType.FLEXIBLE)) {
updateManager.startUpdateFlowForResult(
info, activityResultLauncher, AppUpdateOptions.newBuilder(AppUpdateType.FLEXIBLE).build()
)
}
डाउनलोड पूरा होने पर, InstallStateUpdatedListener InstallStatus.DOWNLOADED रिपोर्ट करता है, और आप इंस्टॉल को साफ़ तौर पर पूरा करते हैं:
updateManager.registerListener { state ->
if (state.installStatus() == InstallStatus.DOWNLOADED) {
updateManager.completeUpdate() // shows the snackbar's restart action
}
}
इमीडिएट इसका उल्टा है: एक फ़ुल-स्क्रीन, ब्लॉकिंग फ़्लो जो यूज़र को तब तक ऐप इस्तेमाल करने से रोकता है जब तक अपडेट इंस्टॉल नहीं हो जाता। यह जानबूझकर बाधा डालने वाला है, जो इसे बिल्कुल एक ही स्थिति के लिए उपयुक्त बनाता है — मौजूदा वर्ज़न किसी ऐसे तरीके से टूटा हुआ है जो वाकई मायने रखता है। एक सिक्योरिटी फिक्स, एक Room स्कीमा माइग्रेशन जिसे पुराना क्लाइंट करप्ट कर देगा, एक बैकएंड API बदलाव जिससे पुरानी बिल्ड हर रिक्वेस्ट पर क्रैश हो जाए। अगर आप यह समझा नहीं सकते कि किसी को एक और दिन पुराना वर्ज़न इस्तेमाल करने देना वाकई नुकसानदेह क्यों है, तो यह इमीडिएट अपडेट नहीं, फ़्लेक्सिबल अपडेट है।
if (info.isUpdateTypeAllowed(AppUpdateType.IMMEDIATE)) {
updateManager.startUpdateFlowForResult(
info, activityResultLauncher, AppUpdateOptions.newBuilder(AppUpdateType.IMMEDIATE).build()
)
}
मैंने इसे सिर्फ़ एक बार इस्तेमाल किया, Granyn की एक रिलीज़ शिप करते समय जिसने यह बदल दिया कि रिकरिंग ट्रांज़ैक्शन कैसे स्टोर होते हैं — नए स्कीमा में लिखने वाला एक पुराना क्लाइंट चुपचाप बैलेंस गलत कैलकुलेट कर देता। यह ब्लॉक करने लायक है। एक बटन का नाम बदलना नहीं।
प्रायोरिटी (और स्टेलनेस) तय करना
AppUpdateInfo फ़्लेक्सिबल/इमीडिएट का चुनाव अंदाज़े पर छोड़ने के बजाय आपको दो सिग्नल देता है:
updatePriority()— 0 से 5 के बीच एक इंटीजर जिसे आप खुद, हर रिलीज़ के लिए, Play Console में रोलआउट के समय सेट करते हैं। यह डिफ़ से कैलकुलेट नहीं होता; आप ही Google को (और अपने क्लाइंट कोड को) बताते हैं कि यह रिलीज़ कितनी अर्जेंट है।clientVersionStalenessDays()— कि यह अपडेट इस खास यूज़र के लिए कितने दिनों से उपलब्ध है, जो इससे अलग है कि आपने इसे कब शिप किया था। एक स्टैगर्ड रोलआउट का मतलब है कि यह नंबर एक ही रिलीज़ के लिए भी आपके इंस्टॉल बेस में अलग-अलग होता है।
एक पैटर्न जो मेरे लिए अच्छी तरह काम करता रहा है: प्रायोरिटी 4-5 को इमीडिएट-योग्य मानें, और बाकी सबके लिए प्रायोरिटी को स्टेलनेस के साथ मिलाएं — प्रायोरिटी-3 रिलीज़ तभी इमीडिएट बनती है जब वह एक-दो हफ्ते बिना क्लेम किए पड़ी रहे, ताकि यूज़र्स को बाधित करने से पहले उन्हें एक ग्रेस पीरियड मिल सके।
val shouldForce = info.updatePriority() >= 4 ||
(info.updatePriority() == 3 && (info.clientVersionStalenessDays() ?: 0) > 14)
वह चेक जिसे हर कोई भूल जाता है: अटके हुए अपडेट को फिर से शुरू करना
एक इमीडिएट अपडेट बाधित हो सकता है — यूज़र ऐप को बैकग्राउंड में भेज देता है, प्रोसेस मर जाती है, कोई फ़ोन कॉल आ जाती है। जब ऐसा होता है, Play Core इसे आपके लिए चुपचाप फिर से शुरू नहीं करता; आपको संबंधित स्क्रीन के हर बार रीज़्यूम होने पर अटके हुए अपडेट की जांच करनी होती है, न कि सिर्फ़ लॉन्च पर एक बार:
override fun onResume() {
super.onResume()
updateManager.appUpdateInfo.addOnSuccessListener { info ->
if (info.updateAvailability() == UpdateAvailability.DEVELOPER_TRIGGERED_UPDATE_IN_PROGRESS) {
updateManager.startUpdateFlowForResult(
info, activityResultLauncher, AppUpdateOptions.newBuilder(AppUpdateType.IMMEDIATE).build()
)
}
}
}
इसे छोड़ दें तो आपको वह फेलियर मोड मिलता है जो इस API को उसकी बदनामी देता है: एक यूज़र अपडेट के बीच में बाधित होता है, किसी टेक्स्ट का जवाब देने के लिए ऐप को बैकग्राउंड में भेजता है, वापस आता है, और ऐप बस… फिर से काम करने लगता है, आधा-अपडेटेड, ऐसी स्थिति में जिसे आपने कभी टेस्ट नहीं किया। onResume चेक सिर्फ़ तीन लाइनों का है और यही एक मज़बूत इमीडिएट अपडेट और एक ऐसी छिटपुट बग रिपोर्ट के बीच का फ़र्क है जिसे आप दोबारा पैदा नहीं कर सकते।
असली रोलआउट का इंतज़ार किए बिना टेस्ट करना
आप अपनी खुद की डिवाइस से, अपनी मौजूदा इंस्टॉल्ड बिल्ड के खिलाफ़, कोई असली अपडेट ट्रिगर नहीं कर सकते — Play Core को आपके अकाउंट को दिखने वाला एक वास्तविक वर्ज़न अंतर चाहिए होता है। व्यावहारिक सेटअप एक इंटरनल टेस्टिंग ट्रैक है: वहां से एक पुराना वर्ज़न कोड इंस्टॉल करें, फिर एक नया पब्लिश करें और API को असली अंतर देखने दें। Google AppUpdateInfo रिस्पॉन्स को लोकली फ़ेक करने के लिए एक इन-ऐप अपडेट टेस्टिंग API भी देता है, जिसे अगर आप इस फ़्लो को अक्सर छूते हैं तो एक डीबग मेन्यू में जोड़ना उचित है — प्रायोरिटी/स्टेलनेस लॉजिक पर ही इटरेट करने के लिए यह असली ट्रैक राउंड-ट्रिप से कहीं ज़्यादा तेज़ है।
इस पूरे फ़ीचर को एक ऐसा सेफ्टी नेट मानें जिस पर टिका न रहा जाए, न कि एक UX पैटर्न जिस पर भरोसा किया जाए। इस API का सबसे अच्छा वर्ज़न वही है जो आपके यूज़र्स कभी देखते ही नहीं, क्योंकि ऑटो-अपडेट पहले ही अपना काम कर चुका होता है — फ़्लेक्सिबल और इमीडिएट उन दिनों के लिए हैं जब वह नहीं हुआ।
// संबंधित पठन
जर्नल से और भी
2026 में androidx.startup: ContentProvider के ढेर के बिना लाइब्रेरी इनिशियलाइज़ेशन का क्रम तय करना
Android की App Startup लाइब्रेरी के लिए एक व्यावहारिक गाइड — initializer को एक ContentProvider में समेटना, dependencies घोषित करना, और वो lazy-init मामले जिनकी जगह यह नहीं ले सकती।
2026 में CameraX: कैमरा सेशन लीक किए बिना Preview और ImageAnalysis को बाइंड करना
Android पर CameraX की व्यावहारिक गाइड: Preview और ImageAnalysis को lifecycle से बाइंड करना, सही backpressure रणनीति, और रोटेशन से होने वाली क्रैश।
2026 में Android Photo Picker API: पूरी गैलरी की अनुमति दिए बिना फ़ोटो अटैच करना
Android के Photo Picker API की व्यावहारिक गाइड — सिंगल और मल्टीपल सिलेक्शन, MIME फ़िल्टरिंग, Android 13 से पहले के लिए बैकपोर्ट, और यह READ_MEDIA_IMAGES से बेहतर क्यों है।