20 अप्रैल 2025·8 मिनट पढ़ने में

धीमे कनेक्शनों के लिए Kotlin नेटवर्किंग: टाइमआउट और सुरक्षित रीट्राई

धीमे कनेक्शनों के लिए व्यावहारिक Kotlin नेटवर्किंग: टाइमआउट सेट करें, सुरक्षित कैश करें, डुप्लिकेट से बचते हुए रीट्राई करें, और अस्थिर मोबाइल नेटवर्क पर क्रिटिकल एक्शन्स को सुरक्षित रखें।

धीमे कनेक्शनों के लिए Kotlin नेटवर्किंग: टाइमआउट और सुरक्षित रीट्राई

धीरे और अस्थिर कनेक्शनों पर क्या टूटता है

मोबाइल पर “धीमा” अक्सर “इंटरनेट नहीं” नहीं होता। इसका मतलब एक ऐसा कनेक्शन है जो चलता जरूर है, पर छोटी-छोटी ब्रस्ट्स में। कोई रिक्वेस्ट 8–20 सेकंड ले सकता है, बीच में अटक सकता है, फिर पूरा हो सकता है। या कभी सफल होता है और अगली बार फेल क्योंकि फ़ोन Wi‑Fi से LTE पर गया, सिग्नल कम हुआ, या OS ने ऐप को बैकग्राउंड कर दिया।

“अस्थिर” और भी बदतर है। पैकेट ड्रॉप होते हैं, DNS टाइमआउट होते हैं, TLS हैंडशेक फेल होते हैं, और कनेक्शन रैंडम रूप से रिसेट हो जाते हैं। आप कोड में सब कुछ “सही” कर भी लें तो भी फ़ील्ड में खराब नेटवर्क के कारण फेलियर दिखेंगे क्योंकि नेटवर्क आपके नीचे से बदल रहा होता है।

यहीं पर डिफॉल्ट सेटिंग्स टूट जाती हैं। कई ऐप्स टाइमआउट, रीट्राई और कैशिंग के लिए लाइब्रेरी डिफॉल्ट्स पर निर्भर करते हैं बिना ये तय किए कि असल उपयोगकर्ताओं के लिए “पर्याप्त” क्या है। डिफॉल्ट अक्सर स्थिर Wi‑Fi और तेज़ APIs के लिए ट्यून होते हैं, न कि एक कम्यूटर ट्रेन, लिफ्ट, या भीड़भरे कैफ़े के लिए।

यूज़र्स “सॉकेट टाइमआउट” या “HTTP 503” नहीं बयां करते। वे लक्षण देखते हैं: अनंत स्पिनर, लंबी प्रतीक्षा के बाद अचानक त्रुटियाँ (फिर अगली बार काम हो जाता है), डुप्लिकेट एक्शन (दो बुकिंग, दो ऑर्डर, दो चार्ज), खोए हुए अपडेट, और मिक्स्ड स्टेट जहाँ UI कहता है “failed” पर सर्वर वास्तव में सफल हुआ होता है।

धीमे नेटवर्क छोटे डिजाइन गैप्स को पैसे और भरोसे की समस्या में बदल देते हैं। अगर ऐप “अभी भेज रहा है” को “फेल” या “हो गया” से स्पष्ट रूप से अलग नहीं करता, तो यूज़र फिर से टैप कर देता है। अगर क्लाइंट अंधाधुंध रीट्राई करता है तो डुप्लिकेट बन सकते हैं। अगर सर्वर idempotency सपोर्ट नहीं करता तो एक shaky कनेक्शन कई “सफल” राइट्स पैदा कर सकता है।

“क्रिटिकल एक्शन्स” वे हैं जो एक बार से ज़्यादा नहीं चलना चाहिए और सही होने चाहिए: पेमेंट्स, चेकआउट सबमिशन्स, स्लॉट बुकिंग, पॉइंट ट्रांसफर, पासवर्ड बदलना, शिपिंग एड्रेस सेव करना, क्लेम सबमिट करना, या अप्रूवल भेजना।

वास्तविक उदाहरण: कोई कमजोर LTE पर चेकआउट सबमिट करता है। ऐप रिक्वेस्ट भेजता है, फिर कनेक्शन रिस्पॉन्स आने से पहले कट जाता है। यूज़र एरर देखता है, फिर “Pay” दबा देता है और अब दो रिक्वेस्ट सर्वर तक पहुँचते हैं। बिना स्पष्ट नियमों के ऐप नहीं बता पाता कि उसे रीट्राई करना चाहिए, इंतज़ार करना चाहिए, या रोक देना चाहिए। यूज़र भी नहीं जानता कि फिर से कोशिश करे या नहीं।

कोड बदलने से पहले अपने नियम तय करें

धीमे या अस्थिर कनेक्शनों में ज़्यादातर बग अस्पष्ट नियमों से आते हैं, HTTP क्लाइंट से नहीं। टाइमआउट, कैशिंग या रीट्राई छुने से पहले लिख लें कि आपके ऐप के लिए “सही” क्या है।

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

फिर तय करें कि खराब नेटवर्क में हर स्क्रीन क्या कर सकती है। कुछ स्क्रीन ऑफ़लाइन भी उपयोगी हो सकती हैं (अंतिम प्रोफ़ाइल, पिछले ऑर्डर्स)। अन्य रीड‑ओनली होनी चाहिए या स्पष्ट “फिर से कोशिश करें” स्टेट दिखानी चाहिए (इन्वेंटरी काउंट्स, लाइव प्राइसिंग)। इन अपेक्षाओं को मिलाने से UI भ्रमित हो जाती है और कैशिंग जोखिमभरा बन जाता है।

एक्शन के अनुसार स्वीकार्य प्रतीक्षा समय यूज़र की सोच के मुताबिक तय करें, कोड की सुथरता के नहीं। लॉगिन थोड़ा इंतज़ार सह लेता है; फ़ाइल अपलोड को ज़्यादा समय चाहिए; चेकआउट को तेज़ लगना चाहिए पर सुरक्षित भी होना चाहिए। 30 सेकंड का टाइमआउट कागज़ पर “विश्वसनीय” हो सकता है पर अनुभव में टूटता हुआ लगेगा।

अंत में तय करें कि आप क्या डिवाइस पर स्टोर करेंगे और कितने समय के लिए। कैश्ड डेटा मदद करता है पर stale डेटा गलत चुनाव करा सकता है (पुरानी कीमतें, समाप्त हुई पात्रता)।

किसी जगह (README ठीक है) ये नियम लिखें और सरल रखें:

  • कौन से एंडपॉइंट्स “दोहराए नहीं जाने चाहिए” और idempotency हैंडलिंग चाहते हैं?
  • कौन सी स्क्रीन ऑफ़लाइन काम करनी चाहिए और कौन सी ऑफ़लाइन रीड‑ओनली होंगी?
  • हर एक्शन के लिए अधिकतम प्रतीक्षा समय क्या है (लॉगिन, फ़ीड रिफ्रेश, अपलोड, चेकआउट)?
  • क्या डिवाइस पर कैश होगा, और एक्सपायरी टाइम क्या होगा?
  • फेल होने पर क्या दिखाएँगे: एरर, बाद में क्यू, या मैनुअल रीट्राय?

जब ये नियम स्पष्ट हों, तो टाइमआउट मान, कैशिंग हैडर्स, रीट्राई पॉलिसी और UI स्टेट्स लागू करना और टेस्ट करना बहुत आसान हो जाता है।

उपयोगकर्ता की उम्मीद से मेल खाने वाले टाइमआउट

धीमे नेटवर्क अलग तरह से फेल करते हैं। अच्छा टाइमआउट सेटअप सिर्फ “कोई नंबर चुनना” नहीं है—यह उस बात से मेल खाता है जो यूज़र करना चाहता है और इतनी जल्दी फेल होता है कि ऐप रिकवर कर सके।

तीन टाइमआउट्स, आम भाषा में:

  • Connect timeout: सर्वर से कनेक्शन बनाने के लिए कितना इंतज़ार करेंगे (DNS, TCP, TLS)। अगर यह फेल होता है तो रिक्वेस्ट असल में कभी शुरू नहीं हुआ माना जाता।
  • Write timeout: रिक्वेस्ट बॉडी भेजते समय कितना इंतज़ार (अपलोड्स, बड़े JSON, धीमा अपलिंक)।
  • Read timeout: रिक्वेस्ट भेजने के बाद सर्वर से डेटा आने तक कितना इंतज़ार। यह अक्सर spotty मोबाइल नेटवर्क पर सामने आता है।

टाइमआउट्स स्क्रीन और जोखिम के अनुसार होने चाहिए। फ़ीड धीरे होने पर नुकसान कम है। क्रिटिकल एक्शन या तो साफ़ पूरा होना चाहिए या स्पष्ट रूप से फेल होना चाहिए ताकि यूज़र अगला कदम तय कर सके।

एक व्यावहारिक शुरुआती बिंदु (मापने के बाद समायोजित करें):

  • लिस्ट लोडिंग (कम जोखिम): connect 5–10s, read 20–30s, write 10–15s.
  • टाइप करते हुए सर्च: connect 3–5s, read 5–10s, write 5–10s.
  • क्रिटिकल एक्शन्स (हाई रिस्क, जैसे “Pay” या “Submit order”): connect 5–10s, read 30–60s, write 15–30s.

संगति परफ़ेक्टनेस से ज्यादा मायने रखती है। अगर यूज़र “Submit” दबाकर दो मिनट स्पिन देखता है तो वह फिर से टैप कर देगा।

"इन्फिनिट लोडिंग" से बचें—UI में भी एक स्पष्ट ऊपरी सीमा दें। तुरंत प्रोग्रेस दिखाएँ, कैंसिल की अनुमति दें, और (माल लें) 20–30 सेकंड के बाद "अभी भी कोशिश कर रहे हैं…" दिखाएँ और retry या connection चेक के विकल्प दें। इससे अनुभव ईमानदार रहता है भले ही नेटवर्क लाइब्रेरी अभी भी प्रतीक्षा कर रही हो।

जब टाइमआउट होता है, डिबग के लिए पर्याप्त लॉग रखें पर सीक्रेट्स लॉग न करें। उपयोगी फ़ील्ड्स में URL path (पूरा query नहीं), HTTP मेथड, स्थिति (यदि कोई है), टाइमिंग ब्रेकडाउन (connect vs write vs read), नेटवर्क टाइप (Wi‑Fi, सेलुलर), अनुमानित रिक्वेस्ट/रिस्पॉन्स साइज, और एक रिक्वेस्ट ID शामिल हैं ताकि क्लाइंट और सर्वर लॉग मिलाए जा सकें।

एक सरल, संगत Kotlin नेटवर्किंग सेटअप

धीमे कनेक्शनों पर क्लाइंट सेटअप में छोटे असंगतियाँ बड़ी समस्याओं में बदल जाती हैं। एक साफ बेसलाइन डिबग तेज करती है और हर रिक्वेस्ट को एक जैसे नियम देती है।

एक क्लाइंट, एक पॉलिसी

एक जगह से अपना HTTP क्लाइंट बनाना शुरू करें (आमतौर पर एक OkHttpClient जिसे Retrofit इस्तेमाल करता है)। बुनियादी चीज़ें वहीं रखें ताकि हर रिक्वेस्ट एक जैसा बर्ताव करे: डिफॉल्ट हेडर्स (ऐप वर्शन, लोकल, ऑथ टोकन) और स्पष्ट User-Agent, टाइमआउट्स एक बार सेट हों, डिबग के लिए लॉगिंग सक्षम करने का विकल्प, और एक रीट्राई पॉलिसी निर्णय (यहाँ तक कि "कोई ऑटोमैटिक रीट्राई नहीं" भी)।

यहाँ एक छोटा उदाहरण है जो कॉन्फ़िगरेशन को एक फ़ाइल में रखता है:

val okHttp = OkHttpClient.Builder()
  .connectTimeout(10, TimeUnit.SECONDS)
  .readTimeout(20, TimeUnit.SECONDS)
  .writeTimeout(20, TimeUnit.SECONDS)
  .callTimeout(30, TimeUnit.SECONDS)
  .addInterceptor { chain ->
    val request = chain.request().newBuilder()
      .header("User-Agent", "MyApp/${BuildConfig.VERSION_NAME}")
      .header("Accept", "application/json")
      .build()
    chain.proceed(request)
  }
  .build()

val retrofit = Retrofit.Builder()
  .baseUrl(BASE_URL)
  .client(okHttp)
  .addConverterFactory(MoshiConverterFactory.create())
  .build()

केंद्रीकृत एरर हैंडलिंग जो यूज़र मैसेज से मैप करे

नेटवर्क एरर्स सिर्फ "एक Exception" नहीं होते। अगर हर स्क्रीन उन्हें अलग तरह से हैंडल करेगी तो यूज़र्स को अलग-अलग मैसेज दिखेंगे।

एक मैपर बनाएँ जो फेलियर्स को कुछ यूज़र‑फ्रेंडली परिणामों में बदले: कोई कनेक्शन नहीं/एयरप्लेन मोड, टाइमआउट, सर्वर एरर (5xx), वेलिडेशन या ऑथ एरर (4xx), और एक अज्ञात fallback।

इससे UI कॉपी कॉन्सिस्टेंट रहती है ("No connection" बनाम "Try again") बिना तकनीकी विवरण लीक हुए।

स्क्रीन बंद होते ही रिक्वेस्ट को टैग और कैंसिल करें

अस्थिर नेटवर्क में कॉल्स देर से फिनिश कर के ऐसी स्क्रीन अपडेट कर सकते हैं जो अब मौजूद नहीं है। कैंसिलेशन को स्टैंडर्ड नियम बनाइए: जब स्क्रीन बंद हो, उसका काम रुक जाए।

Retrofit और Kotlin coroutines के साथ, coroutine scope को cancel करने पर अंतर्निहित HTTP कॉल भी कैंसिल हो जाता है (उदाहरण के लिए ViewModel में)। नॉन‑कोरौटीन कॉल्स के लिए Call का रेफरेंस रखें और cancel() कॉल करें। आप रिक्वेस्ट्स को टैग भी कर सकते हैं और फीचर से बाहर निकलते समय कॉल्स के समूह कैंसिल कर सकते हैं।

बैकग्राउंड वर्क UI पर निर्भर न हो

जो भी महत्वपूर्ण काम पूरा होना चाहिए (रिपोर्ट भेजना, क्यू सिंक करना, सबमिशन फिनिश करना) उसे ऐसे शेड्यूलर में चलाएँ जो इसके लिए बनाया गया हो। Android पर WorkManager आमतौर पर उपयुक्त है क्योंकि यह बाद में रीट्राय कर सकता है और ऐप रिस्टार्ट पर जीवित रह सकता है। UI एक्शन्स को हल्का रखें और जहाँ उपयुक्त हो लंबे काम को बैकग्राउंड जॉब्स को सौंप दें।

मोबाइल पर सुरक्षित कैशिंग के नियम

नियमों को API में बदलें
डेटा मॉडल और API एंडपॉइंट्स विज़ुअली डिजाइन करें, फिर क्लाइंट्स पर संगत व्यवहार जारी करें।
API बनाएं

कैशिंग धीमे कनेक्शनों पर बड़ा फायदा देता है क्योंकि यह रिपीट डाउनलोड घटाता है और स्क्रीन फास्ट दिखती हैं। पर यह समस्या भी बन सकता है अगर यह गलत समय पर stale डेटा दिखाए, जैसे पुरानी बैलेंस या आउटडेटेड डिलीवरी एड्रेस।

सुरक्षित तरीका यह है कि केवल वही कैश करें जो यूज़र थोड़ी पुरानी होने पर सहन कर सके, और पैसे/सिक्योरिटी/अंतिम फैसले पर ताज़ा चेक ज़रूरी रखें।

Cache-Control बेसिक्स जिन पर आप भरोसा कर सकते हैं

अधिकतर नियम कुछ हैडर्स पर आते हैं:

  • max-age=60: आप बिना सर्वर से पूछे 60 सेकंड तक cached response फिर से इस्तेमाल कर सकते हैं।
  • no-store: इस रिस्पॉन्स को कतई सेव न करें (टोकन और संवेदनशील स्क्रीन के लिए बेहतर)।
  • must-revalidate: अगर एक्सपायर हो चुका है तो इसे फिर से यूज़ करने से पहले सर्वर से चेक करना होगा।

मोबाइल पर must-revalidate अस्थायी ऑफ़लाइन अवधि के बाद “चुपके से गलत” डेटा दिखने से रोकता है। अगर यूज़र सबवे से बाहर आकर ऐप खोलता है तो आपको तेज़ स्क्रीन चाहिए पर साथ ही ऐप को यह भी सुनिश्चित करना होगा कि जो दिख रहा है वह अभी भी सही है।

ETag रिफ्रेश: तेज़, सस्ता और भरोसेमंद

रीड एंडपॉइंट्स के लिए, ETag‑आधारित वैलिडेशन अक्सर लंबे max-age से बेहतर होता है। सर्वर रिस्पॉन्स के साथ ETag भेजता है। अगली बार ऐप If-None-Match के साथ वह वैल्यू भेजता है। अगर कुछ नहीं बदला तो सर्वर 304 Not Modified लौटाता है, जो कमजोर नेटवर्क पर छोटा और तेज़ होता है।

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

सरल नियम:

  • रीड एंडपॉइंट्स को छोटे max-age के साथ must-revalidate और संभव हो तो ETag सपोर्ट करें।
  • राइट एंडपॉइंट्स (POST/PUT/PATCH/DELETE) को कैश न करें। इन्हें हमेशा नेटवर्क‑बाउंड मानें।
  • संवेदनशील चीज़ों (ऑथ रिस्पॉन्स, पेमेंट स्टेप्स, प्राइवेट मैसेजेस) के लिए no-store का उपयोग करें।
  • स्टेटिक एसेट्स (आइकॉन्स, पब्लिक कॉन्फ़िग) को अधिक समय तक कैश करें क्योंकि stale होने का जोखिम कम है।

कैशिंग निर्णय पूरे ऐप में सुसंगत रखें। यूज़र असंगतियों को छोटे विलंबों से ज्यादा नोटिस करते हैं।

नुकसान न बढ़ाएं—सुरक्षित रीट्राइ

नेटिव ऐप्स तेज़ी से शिप करें
धीमे कनेक्शनों को संभालने वाले नेटिव Android और iOS ऐप तेज़ी से बनाएं।
मोबाइल बनाएं

रीट्राइ एक आसान समाधान जैसा लग सकता है पर गलत तरीके से किया तो नुकसान कर सकता है। गलत रिक्वेस्ट्स को रीट्राइ करने पर अतिरिक्त लोड, बैटरी ड्रेन और ऐप का फँसना हो सकता है।

शुरुआत केवल उन फेलियर्स को रीट्राइ करने से करें जो अस्थायी होने की संभावना रखते हैं। कटा कनेक्शन, रीड टाइमआउट, या छोटा सर्वर आउटेज अगली बार सफल हो सकता है। गलत पासवर्ड, मिसिंग फील्ड, या 404 पर रीट्राइ बेअसर होगा।

व्यावहारिक नियम:

  • टाइमआउट्स और कनेक्शन फेलियर्स को रीट्राइ करें।
  • 502, 503 और कभी-कभी 504 को रीट्राइ करें।
  • 4xx न रीट्राइ करें (408 या 429 पर स्पष्ट वेट नियम हो तो छोड़ कर)।
  • ऐसे रिक्वेस्ट्स न रीट्राइ करें जो पहले ही सर्वर तक पहुँच चुके हैं और प्रोसेस हो सकते हैं।
  • रीट्राइ कम रखें (आमतौर पर 1–3 प्रयास)।

बैकऑफ़ + जिटर: रीट्राई स्टॉर्म घटाएँ

अगर कई यूज़र्स एक ही आउटेज का सामना कर रहे हों तो एकसाथ रीट्राइ ट्रैफ़िक की लहर बना सकते हैं जो रिकवरी को धीमा कर दे। एक्सपोनेंशियल बैकऑफ़ का उपयोग करें और थोड़ी रैंडम जिटर जोड़ें ताकि डिवाइसेज एक ही समय पर रीट्राइ न करें।

उदाहरण: लगभग 0.5s के बाद, फिर 1s, फिर 2s प्रतीक्षा करें, हर बार ±20% यादृच्छिकता के साथ।

कुल रीट्राइ समय पर कैप लगाएँ

बिना सीमा के रीट्राइ यूज़र्स को मिनटों तक स्पिनर में फँसा सकते हैं। पूरे ऑपरेशन के लिए अधिकतम कुल समय चुनें, जिसमें सभी वेट शामिल हों। कई ऐप्स 10–20 सेकंड लक्ष्य रखते हैं फिर वे रुक कर स्पष्ट विकल्प दिखाते हैं।

संदर्भ के अनुसार मेल भी रखें—यदि कोई फॉर्म सबमिट कर रहा है तो वह तुरंत उत्तर चाहता है; यदि बैकग्राउंड सिंक फेल हुआ है तो आप बाद में रीट्राइ कर सकते हैं।

गैर‑idempotent एक्शन्स (जैसे ऑर्डर प्लेस करना या पेमेंट भेजना) को कभी ऑटो‑रीट्राइ न करें जब तक आपके पास idempotency की सुरक्षा न हो। अगर सुरक्षा गारंटीकृत नहीं है तो साफ़ फेल दिखाएँ और यूज़र को निर्णय दें।

क्रिटिकल एक्शन्स के लिए डुप्लिकेट‑रोध

धीमे या अस्थिर कनेक्शन पर यूज़र दो बार टैप कर देते हैं। OS बैकग्राउंड में रीट्राई कर सकता है। आपका ऐप टाइमआउट के बाद फिर से भेज सकता है। अगर एक्शन "किसी चीज़ को बनाना" है (ऑर्डर, पैसे भेजना, पासवर्ड बदलना), तो डुप्लिकेट नुकसान कर सकते हैं।

Idempotency का अर्थ है कि एक ही रिक्वेस्ट से एक ही परिणाम हो। अगर रिक्वेस्ट दोहराई गई तो सर्वर दूसरे ऑर्डर को न बनाए; वह पहले वाला रिज़ल्ट लौटाए या कहे "पहले से हो चुका है।"

हर क्रिटिकल प्रयास के लिए idempotency की रखें

क्रिटिकल एक्शन्स के लिए, जब यूज़र प्रयास शुरू करे तब एक यूनिक idempotency की जेनरेट करें और उसे रिक्वेस्ट के साथ भेजें (आमतौर पर Idempotency-Key हेडर या बॉडी का फ़ील्ड)।

एक व्यावहारिक फ्लो:

  • यूज़र ने “Pay” दबाया तो UUID idempotency की बनाएं।
  • इसे लोकली छोटे रेकॉर्ड के साथ सेव करें: status = pending, createdAt, request payload hash.
  • की के साथ रिक्वेस्ट भेजें।
  • सफलता मिलने पर status = done मार्क करें और सर्वर का result ID स्टोर करें।
  • अगर रीट्राइ करना पड़े तो वही की फिर से उपयोग करें, नई की न बनाएं।

"इसी की का फिर से उपयोग" नियम ही आकस्मिक डबल चार्ज को रोकता है।

ऐप रिस्टार्ट और ऑफ़लाइन गैप्स को हैंडल करें

अगर ऐप रिक्वेस्ट के बीच मेंKilled हो जाए, तो अगली लॉन्च पर भी सुरक्षित रहना चाहिए। idempotency की और रिक्वेस्ट स्टेट लोकल स्टोरेज (छोटे DB रो) में रखें। रिस्टार्ट पर या तो उसी की से रीट्राइ करें या सेव्ड की/रिज़ल्ट ID के साथ "स्टेट चेक" एंडपॉइंट कॉल करें।

सर्वर‑साइड कॉन्ट्रैक्ट स्पष्ट होना चाहिए: डुप्लीकेट की मिलने पर या तो दूसरा प्रयास अस्वीकार करे या पहला रिस्पॉन्स लौटाए (उसी ऑर्डर ID, उसी रसीद के साथ)। अगर सर्वर ऐसा नहीं कर सकता तो क्लाइंट‑साइड डुप्लिकेट‑रोध कभी पूरी तरह भरोसेमंद नहीं होगा, क्योंकि ऐप नहीं देख सकता कि भेजने के बाद सर्वर पर क्या हुआ।

यूज़र‑फेसिंग टच: अगर प्रयास pending है तो दिखाएँ “Payment in progress” और बटन डिसेबल करें जब तक अंतिम परिणाम न मिल जाए।

UI पैटर्न जो आकस्मिक रिसबमिट्स घटाते हैं

लॉजिक को सही जगह रखें
बिजनेस नियम सर्वर-साइड रखें ताकि रीट्राई सुरक्षित रहें और डुप्लिकेट रोकना आसान हो।
बैकएंड बनाएं

धीमे कनेक्शन सिर्फ रिक्वेस्ट्स ही नहीं तोड़ते; वे लोगों के टैप करने के तरीके बदल देते हैं। जब स्क्रीन 2 सेकंड के लिए फ्रीज़ हो तो कई यूज़र मान लेते हैं कि कुछ नहीं हुआ और फिर से बटन दबा देते हैं। आपका UI “एक टैप” को भरोसेमंद बनाना चाहिए भले ही नेटवर्क भरोसेमंद न हो।

Optimistic UI उन एक्शन्स के लिए सुरक्षित है जो reversible या कम जोखिम वाले हों—स्टार करना, ड्राफ्ट सेव करना, या संदेश को पढ़ना। पैसे, इन्वेंटरी, अपरिवर्तनीय डिलीट्स और जिनसे डुप्लिकेट बन सकते हैं उनके लिए Confirmed UI बेहतर है।

क्रिटिकल एक्शन्स के लिए अच्छा डिफॉल्ट एक स्पष्ट pending स्टेट है। पहली टैप पर प्राथमिक बटन तुरंत “Submitting…” में बदलें, उसे डिसेबल करें, और एक छोटा टेक्स्ट दिखाएँ जो बता दे क्या हो रहा है।

फ्लेकि नेटवर्क्स पर काम करने वाले पैटर्न:

  • टैप के बाद प्राथमिक एक्शन डिसेबल करें और अंतिम रिज़ल्ट मिलने तक उसे डिसेबल रखें।
  • अमाउंट, रिसीपिएंट, आइटम काउंट जैसे विवरण के साथ एक दिखने वाला “Pending” स्टेट दिखाएँ।
  • एक सरल “Recent activity” दृश्य रखें ताकि यूज़र देख सके उन्होंने क्या भेजा था।
  • अगर ऐप बैकग्राउंड हुआ तो लौटने पर pending स्टेट कायम रखें।
  • एक ही स्क्रीन पर कई टैप टार्गेट्स की जगह एक स्पष्ट प्राथमिक बटन रखें।

कभी-कभी रिक्वेस्ट सफल हो जाती है पर रिस्पॉन्स खो जाता है। इसे सामान्य परिणाम मानें, एक ऐसी त्रुटि न बनाएं जो बार‑बार टैप को प्रोत्साहित करे। "Failed, try again" की जगह दिखाएँ "हम अभी निश्चित नहीं हैं" और सुरक्षित अगला कदम जैसे "स्टेटस चेक करें" दें। अगर आप स्टेटस चेक नहीं कर सकते तो लोकली pending रिकॉर्ड रखें और यूज़र को बताएं कि आप कनेक्शन लौटने पर अपडेट कर देंगे।

"Try again" तभी दिखाएँ जब आप वही क्लाइंट-साइड रिक्वेस्ट ID या idempotency की उपयोग कर के सुरक्षित रूप से अनुरोध दोहरा सकें।

एक वास्तविक उदाहरण: अस्थिर चेकआउट सबमिशन

सोर्स कोड अपने पास रखें
वास्तविक सोर्स कोड प्राप्त करें जिसे आप एक्सपोर्ट, रिव्यू और अपनी ज़रूरत के अनुसार डिप्लॉय कर सकें।
सोर्स कोड बनाएं

एक ग्राहक ट्रेन पर है जहाँ सिग्नल बुरे हैं। उन्होंने कार्ट में आइटम जोड़े और Pay दबाया। ऐप को धैर्य रखना है पर साथ ही दो ऑर्डर नहीं बननी चाहिए।

एक सुरक्षित अनुक्रम इस तरह दिखता है:

  1. ऐप क्लाइंट‑साइड प्रयास ID बनाता है और चेकआउट रिक्वेस्ट idempotency की के साथ भेजता है (उदाहरण के लिए UUID जिसे कार्ट के साथ स्टोर किया गया)।
  2. रिक्वेस्ट कनेक्ट टाइमआउट के लिए प्रतीक्षा करता है और फिर लंबे रीड टाइमआउट का सहारा लेता है। ट्रेन सुरंग में चली जाती है और कॉल टाइमआउट हो जाता है।
  3. ऐप एक बार रीट्राइ करता है, पर केवल तब जब उसे सर्वर रिस्पॉन्स कभी नहीं मिला।
  4. सर्वर दूसरी रिक्वेस्ट प्राप्त करता है और वही idempotency की देख कर मूल परिणाम लौटाता है, बजाय नए ऑर्डर के।
  5. ऐप सफलता रिस्पॉन्स मिलने पर अंतिम पुष्टि स्क्रीन दिखाता है, चाहे वह रीट्राइ से मिली हुई रिस्पॉन्स ही क्यों न हो।

कैशिंग कड़े नियमों का पालन करती है। प्रोडक्ट लिस्ट्स, डिलीवरी विकल्प और टैक्स तालिकाएँ शॉर्ट‑टर्म के लिए कैश हो सकती हैं (GET रिक्वेस्ट्स)। चेकआउट सबमिशन (POST) कभी कैश न करें। अगर आप HTTP कैश इस्तेमाल भी करते हैं तो उसे ब्राउज़िंग के लिए रीड‑ओनली मदद समझें, पेमेंट याद रखने वाली चीज़ न मानें।

डुप्लिकेट‑रोध नेटवर्क और UI दोनों के चुनाव का मिश्रण है। जब यूज़र Pay दबाता है तो बटन डिसेबल हो जाता है और स्क्रीन “Submitting order...” दिखाती है, साथ में एक Cancel ऑप्शन। अगर नेटवर्क खो जाए तो यह “Still trying” में बदलती है और वही प्रयास ID बनी रहती है। अगर यूज़र फोर्स‑क्लोज कर के फिर खोलता है तो ऐप उसी ID से ऑर्डर स्टेट चेक कर के रिज़्यूम कर सकता है बजाय यह पूछने के कि क्या वे फिर से पे करें।

त्वरित चेकलिस्ट और अगले कदम

अगर आपका ऐप ऑफिस Wi‑Fi पर “ठीक” लगता है पर ट्रेन, लिफ्ट, या देहाती इलाकों में टूट जाता है, तो इसे रिलीज़‑गेट मानें। यह काम ज़्यादातर चतुर कोड से नहीं बल्कि स्पष्ट नियम बनाने से होता है जो दोहराए जा सकें।

शिप करने से पहले चेकलिस्ट:

  • एंडपॉइंट प्रकार के अनुसार टाइमआउट सेट करें (लॉगिन, फ़ीड, अपलोड, चेकआउट) और throttled/high‑latency नेटवर्क पर टेस्ट करें।
  • केवल वहीं रीट्राइ करें जहाँ यह वाकई सुरक्षित हो, और बैकऑफ़ के साथ इसे कैप करें (रीड्स के लिए कुछ कोशिशें, राइट्स के लिए आमतौर पर नहीं)।
  • हर क्रिटिकल राइट के लिए idempotency की जोड़ें (पेमेंट्स, ऑर्डर, फॉर्म सबमिशन) ताकि रीट्राइ या डबल‑टैप डुप्लिकेट न बनाए।
  • कैशिंग नियम स्पष्ट करें: क्या stale चल सकता है, क्या ताज़ा होना चाहिए, और क्या कभी कैश न हो।
  • स्टेट्स दिखाएँ: pending, failed, completed अलग दिखें, और ऐप रिस्टार्ट के बाद भी पूरा हुआ काम याद रहे।

अगर इन में से कोई भी "बाद में तय करेंगे" है, तो स्क्रीन‑टू‑स्क्रीन यादृच्छिक व्यवहार मिलेगा।

इसे कायम करने के लिए अगले कदम

एक पन्ने की नेटवर्किंग पॉलिसी लिखें: एंडपॉइंट श्रेणियाँ, टाइमआउट टार्गेट, रीट्राई नियम, और कैशिंग अपेक्षाएँ। इसे एक जगह लागू करें (इंटरसेप्टर्स, साझा क्लाइंट फ़ैक्टरी, या छोटी व्रैपर) ताकि हर टीम मेंबर को डिफ़ॉल्ट रूप से वही व्यवहार मिले।

फिर एक छोटा डुप्लिकेट ड्रिल करें। एक क्रिटिकल एक्शन चुनें (जैसे चेकआउट), फ्रीज़्ड स्पिनर सिम्युलेट करें, ऐप फोर्स‑क्लोज करें, एयरप्लेन मोड टॉगल करें, और फिर बटन दबाएँ। अगर आप साबित नहीं कर पाते कि यह सुरक्षित है तो यूज़र्स अंततः इसे तोड़ ही देंगे।

यदि आप यही नियम बैकएंड और क्लाइंट्स पर बिना बहुत हाथ‑माइक्रोमैनेज किए लागू करना चाहते हैं तो AppMaster (appmaster.io).production‑ready बैकएंड और नेटिव मोबाइल सोर्स कोड जेनरेट करने में मदद कर सकता है। फिर भी, असली बात पॉलिसी है: idempotency, रीट्राइ, कैशिंग और UI स्टेट्स को एक बार परिभाषित करें और पूरे फ्लो में सुसंगत रूप से लागू करें।

सामान्य प्रश्न

टाइमआउट और रीट्राइ बदलने से पहले पहले मुझे क्या करना चाहिए?

पहले यह तय करें कि हर स्क्रीन और हर एक्शन के लिए “सही” क्या है, खासकर जो एक बार से ज़्यादा नहीं चलना चाहिए — जैसे कि पेमेंट या ऑर्डर। नियम साफ़ होने के बाद ही टाइमआउट, रीट्राई, कैशिंग और UI स्टेट्स को उन नियमों के अनुरूप सेट करें, न कि लाइब्रेरी डिफॉल्ट्स पर भरोसा कर के।

धीमे या अस्थिर नेटवर्क पर यूज़र्स अक्सर किन समस्याओं का सामना करते हैं?

यूज़र अक्सर अनंत स्पिनर, लंबी प्रतीक्षा के बाद त्रुटियाँ, दूसरी बार पर काम करने वाले एक्शन, या डुप्लिकेट परिणाम (दो ऑर्डर, दो चार्ज) देखते हैं। अक्सर वजह अस्पष्ट रीट्राई नीतियाँ और “पेंडिंग बनाम फेल्ड” नियमों का अभाव होता है, न कि सिर्फ खराब सिग्नल।

मोबाइल पर connect, read, और write टाइमआउट्स के बारे में मुझे कैसे सोचना चाहिए?

Connect timeout कनेक्शन बनाने के लिए है, write timeout अनुरोध बॉडी भेजने (अपलोड) के लिए और read timeout प्रतिक्रिया के आने तक प्रतीक्षा करने के लिए। कम जोखिम वाले रीड्स के लिए छोटे टाइमआउट और क्रिटिकल सबमिशन के लिए लंबे रीड/राइट टाइमआउट रखें, और UI में एक स्पष्ट सीमा रखें ताकि यूज़र अनंत प्रतीक्षा में न फँसे।

अगर मैं OkHttp में सिर्फ एक टाइमआउट सेट कर सकता हूँ, तो किसे चुनूँ?

हाँ—अगर आप केवल एक सेट कर सकते हैं तो callTimeout का उपयोग करें ताकि पूरे ऑपरेशन के लिए एक एंड-टू-एंड कैप हो और “अनंत” प्रतीक्षा न हो। फिर जरूरत के अनुसार connect/read/write टाइमआउट्स डालें, खासकर अपलोड और धीमी रिस्पॉन्स बॉडीज़ के लिए।

कौन सी त्रुटियाँ आम तौर पर सुरक्षित हैं रीट्राई करने के लिए, और कौन सी नहीं?

अस्थायी फेलियर्स जैसे कनेक्शन ड्रॉप, DNS इश्यू और टाइमआउट्स को रीट्राइ करें; कभी-कभी 502/503/504 को भी। 4xx को रीट्राइ न करें (408 या 429 के अपवाद पर स्पष्ट वेट नियम होने पर विचार करें)। और ऐसे अनुरोधों को न दोहराएँ जो सर्वर तक पहुँच गए हैं और प्रोसेस हो रहे हो—अन्यथा डुप्लिकेट बन सकते हैं।

मैं रीट्राइ कैसे जोड़ूँ ताकि ऐप अटका हुआ न लगे?

कम प्रयासों के साथ exponential backoff और थोड़ी jitter (छोटी यादृच्छिक देरी) रखें ताकि कई डिवाइसेज एक साथ रीट्राइ न करें। कुल रीट्राइ समय पर भी कैप लगाएँ ताकि यूज़र लंबी स्पिनर में फँस न जाए।

इडेम्पोटेंसी क्या है, और यह पेमेंट्स और ऑर्डर्स के लिए क्यों मायने रखती है?

इडेम्पोटेंसी का अर्थ है कि एक ही अनुरोध दोहराने पर दूसरा परिणाम नहीं बनना चाहिए — यानी डबल-टैप या रीट्राइ से डबल-चार्ज या डबल-बुकिंग नहीं हो। क्रिटिकल एक्शन्स में प्रत्येक प्रयास के लिए एक idempotency कुंजी भेजें और रीट्राइ में वही कुंजी फिर से उपयोग करें ताकि सर्वर पहले परिणाम को लौटाए न कि नया बनाए।

Android पर मैं idempotency कुंजी कैसे जनरेट और स्टोर करूँ?

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

अविश्वसनीय कनेक्शनों पर मोबाइल ऐप्स के लिए सबसे सुरक्षित कैशिंग नियम कौन से हैं?

केवल ऐसी ही डेटा कैश करें जो थोड़ी पुरानी होने पर भी उपयोगकर्ता के लिए स्वीकार्य हो; पैसे, सुरक्षा और अंतिम निर्णय पर असर डालने वाली चीज़ों के लिए हमेशा ताज़ा चेक कराएँ। रीड्स के लिए कम freshness + revalidation और ETag वगैरह बेहतर हैं; राइट्स को कभी कैश न करें और संवेदनशील प्रतिक्रियाओं के लिए no-store का उपयोग करें।

धीमे नेटवर्क पर डबल-टैप और आकस्मिक रिपामिट्स को कम करने के लिए कौन से UI पैटर्न काम करते हैं?

पहली टैप के बाद प्राथमिक बटन डिसेबल कर दें, तुरंत “Submitting…” स्टेट दिखाएँ और एक दृश्यमान pending स्थिति रखें जो बैकग्राउंडिंग या रीस्टार्ट पर भी जिंदा रहे। अगर रिस्पॉन्स खो सकता है तो यूज़र को बार-बार टैप करने के बजाय "हम अभी निश्चित नहीं हैं" जैसा संदेश दें और सुरक्षित विकल्प जैसे "स्टेटस चेक करें" दिखाएँ।

शुरू करना आसान
कुछ बनाएं अद्भुत

फ्री प्लान के साथ ऐपमास्टर के साथ प्रयोग करें।
जब आप तैयार होंगे तब आप उचित सदस्यता चुन सकते हैं।

शुरू हो जाओ