30 मार्च 2025·8 मिनट पढ़ने में

फ़ील्ड ऐप्स के लिए Kotlin WorkManager बैकग्राउंड सिंक पैटर्न

फ़ील्ड ऐप्स के लिए Kotlin WorkManager बैकग्राउंड सिंक: सही वर्क प्रकार चुनें, constraints सेट करें, exponential backoff का उपयोग करें, और यूज़र-देखने योग्य प्रगति दिखाएँ।

फ़ील्ड ऐप्स के लिए Kotlin WorkManager बैकग्राउंड सिंक पैटर्न

फील्ड और ऑप्स ऐप्स के लिए भरोसेमंद बैकग्राउंड सिंक का मतलब क्या है

फ़ील्ड और ऑप्स ऐप्स में, सिंक “अच्छा तो है” वाली चीज़ नहीं है—यह वही रास्ता है जिससे काम डिवाइस से बाहर जाकर टीम के लिए असली बनता है। जब सिंक फेल होता है तो यूज़र तुरंत नोटिस करते हैं: पूरा हुआ काम अभी भी "pending" दिखना, तस्वीरें गायब होना, या वही रिपोर्ट दो बार अपलोड होना और duplicate बन जाना।

ये ऐप्स सामान्य कंज्यूमर ऐप्स से कठिन होते हैं क्योंकि फोन सबसे खराब हालात में चलते हैं। नेटवर्क LTE, कमजोर Wi‑Fi और नो सिग्नल के बीच झूलता है। बैटरी सेवर बैकग्राउंड काम रोक देता है। ऐप मर जाता है, OS अपडेट होता है, और डिवाइस रूट के बीच रिबूट हो सकता है। एक भरोसेमंद WorkManager सेटअप को इनमें से सब कुछ बिना ड्रामा के झेलना चाहिए।

भरोसेमंद आमतौर पर चार चीज़ें देती है:

  • अंततः निरंतर (Eventually consistent): डेटा देर से आ सकता है, पर मैनुअल देखभाल के बिना आ जाता है।
  • रिकवर करने योग्य (Recoverable): अगर ऐप अपलोड के बीच मर जाए तो अगली रन सुरक्षित तरीके से जारी रखेगी।
  • दिखाई देने योग्य (Observable): यूज़र और सपोर्ट जान सकें क्या हो रहा है और क्या अटका है।
  • गैर-नाशकारी (Non-destructive): retries duplicates या स्टेट को खराब न करें।

"Run now" छोटे, यूज़र-ट्रिगर किए गए काम के लिए है जो जल्द पूरा होना चाहिए (उदा. यूज़र किसी जॉब को बंद करने से पहले एक स्टेटस अपडेट भेजना)। "Wait" भारी काम के लिए है जैसे फोटो अपलोड, बैच अपडेट्स, या कुछ भी जो बैटरी ड्रेन कर सकता है या खराब नेटवर्क पर फेल होने के अधिक अवसर रखता है।

उदाहरण: एक इंस्पेक्टर बिना सिग्नल के बेसमेंट में 12 फोटो के साथ एक फॉर्म सबमिट करता है। भरोसेमंद सिंक हर चीज़ लोकली स्टोर करता है, उसे queued मार्क करता है, और जब डिवाइस कनेक्शन मिले तो बिना इंस्पेक्टर को फिर से काम करने पर मजबूर किए अपलोड कर देता है।

सही WorkManager बिल्डिंग ब्लॉक्स चुनें

सबसे छोटे, सबसे स्पष्ट यूनिट ऑफ वर्क चुनकर शुरू करें। यह निर्णय किसी भी चालाक retry लॉजिक से ज़्यादा reliability को प्रभावित करता है।

One-time बनाम periodic work

OneTimeWorkRequest का उपयोग उन कामों के लिए करें जो किसी बदलाव के कारण होना चाहिए: नया फॉर्म सेव हुआ, फोटो compress हो गया, या यूज़र ने Sync दबाया। इसे constraints के साथ तुरंत enqueue करें और WorkManager को उस समय चलने दें जब डिवाइस तैयार हो।

PeriodicWorkRequest का उपयोग स्थिर रखरखाव के लिए करें, जैसे “updates के लिए चेक” या रात का cleanup। Periodic work सटीक नहीं होता। इसका एक न्यूनतम इंटरवल होता है और यह बैटरी व सिस्टम नियमों के आधार पर drift कर सकता है, इसलिए इसे महत्वपूर्ण अपलोड्स का अकेला रास्ता न बनाएं।

एक व्यावहारिक पैटर्न है: “जरूरी जल्दी सिंक” के लिए one-time work और safety net के रूप में periodic work।

Worker, CoroutineWorker, या RxWorker चुनना

अगर आप Kotlin में हैं और suspend फ़ंक्शन इस्तेमाल करते हैं, तो CoroutineWorker पसंद करें। यह कोड को छोटा रखता है और cancellation उम्मीद के मुताबिक काम करता है।

Worker साधारण ब्लॉकिंग कोड के लिए ठीक है, पर ध्यान रखें कि बहुत लंबा ब्लॉक न करें।

RxWorker तभी समझदारी है जब आपकी ऐप पहले से RxJava का भारी उपयोग करती हो—अन्यथा यह अनावश्यक जटिलता है।

स्टेप्स को chain करें या एक worker में फेज़ बनाएं?

चेन करना तब अच्छा है जब स्टेप्स स्वतंत्र रूप से सफल/असफल हो सकती हैं और आप अलग retries व साफ़ लॉग्स चाहते हैं। एक worker में फ़ेज़ तब बेहतर हो सकता है जब स्टेप्स डेटा साझा करते हों और उन्हें एक ट्रांज़ैक्शन की तरह ट्रीट करना हो।

एक सरल नियम:

  • जब स्टेप्स के अलग constraints हों (जैसे Wi‑Fi पर अपलोड, फिर हल्का API कॉल) तो chain करें।
  • जब पूरा काम "all-or-nothing" होना चाहिए तो एक worker का उपयोग करें।

WorkManager यह गारंटी देता है कि काम persist होगा, process death और reboots को झेल सकता है, और constraints का सम्मान करता है। यह सटीक समय, तुरंत execution, या यूज़र के force-stop के बाद चलना गारंटी नहीं देता। अगर आप Android फील्ड ऐप बना रहे हैं (AppMaster द्वारा Kotlin जेनरेट किए गए ऐप्स सहित), तो सिंक को इस तरह डिजाइन करें कि देरी सुरक्षित और अपेक्षित हो।

सिंक को सुरक्षित बनाएं: idempotent, incremental, और resumable

एक फील्ड ऐप बार-बार काम चलाएगा। फोन सिग्नल खो देते हैं, OS प्रोसेस मार देता है, और यूज़र दो बार sync दबा देते हैं क्योंकि कुछ नहीं हुआ जैसा लगा। अगर आपका बैकग्राउंड सिंक दोहराना सुरक्षित नहीं है, तो आपको duplicate रिकॉर्ड, गायब अपडेट, या अनंत retries मिलेंगे।

हर सर्वर कॉल को दो बार चलाने के लिए सुरक्षित बनाना शुरू करें। सबसे सरल तरीका है प्रति आइटम एक idempotency key (उदा. UUID) रखना जिसे सर्वर "same request, same result" की तरह ट्रीट करे। अगर आप सर्वर बदल नहीं सकते तो stable natural key और upsert endpoint, या version नंबर शामिल करें ताकि सर्वर stale अपडेट reject कर सके।

लोकल स्टेट स्पष्ट रूप से ट्रैक करें ताकि worker crash के बाद अनुमान करने के बजाय बिना रुके resume कर सके। एक साधारण state machine अक्सर काफी रहती है:

  • queued
  • uploading
  • uploaded
  • needs-review
  • failed-temporary

सिंक को incremental रखें। "सब कुछ सिंक करो" की बजाय lastSuccessfulTimestamp या सर्वर द्वारा दिए गए token जैसा cursor रखें। छोटे पेज में बदलाव पढ़ें, लागू करें, और बैच स्थानीय रूप से पूरी तरह commit होने के बाद ही cursor बढ़ाएं। छोटे बैच (20–100 आइटम जैसे) timeouts कम करते हैं, प्रगति दिखाते हैं, और एक interruption के बाद कितनी बार काम दोहराना पड़ेगा उसे सीमित करते हैं।

अपलोड्स को भी resumable बनाएं। फोटो या बड़े payloads के लिए file URI और अपलोड मेटाडेटा persist करें, और केवल सर्वर कन्फर्मेशन के बाद ही uploaded मार्क करें। अगर worker restart हो जाए तो वह आखिरी ज्ञात स्थिति से आगे बढ़ेगा न कि वापस शुरू करेगा।

उदाहरण: एक तकनीशियन 12 फॉर्म भरता है और 8 फोटो अटैच करता है जमीन के नीचे। जब डिवाइस reconnect करता है, worker बैच में अपलोड करता है, हर फॉर्म का idempotency key होता है, और sync cursor हर बैच की सफलता के बाद ही बढ़ता है। अगर ऐप बीच में मर जाए, तो worker फिर से चलकर बाकी queued आइटम बिना डुप्लीकेट बनाए पूरा कर देता है।

वास्तविक डिवाइस स्थितियों के अनुरूप constraints

Constraints वे गार्डरेइल्स हैं जो बैकग्राउंड सिंक को बैटरी ड्रेन करने, डाटा प्लान जलाने, या सबसे खराब समय पर फेल होने से बचाते हैं। आप ऐसी constraints चाहते हैं जो फील्ड में डिवाइस कैसे व्यवहार करते हैं उसे दर्शाए, न कि आपकी मेज़ पर कैसे।

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

ओवर-कंस्ट्रेन करना एक आम वजह है कि "sync कभी नहीं चलता" की शिकायतें आती हैं। अगर आप unmetered Wi‑Fi, charging, और battery not low सभी मांग लेंगे तो आपने मूल रूप से एक परफेक्ट क्षण मांगा है जो शायद कभी न आए। अगर बिज़नेस को आज डाटा चाहिए तो छोटे कामों को बार-बार चलाना बेहतर है बजाय आदर्श परिस्थितियों के इंतज़ार के।

Captive portals एक और वास्तविक समस्या है: फोन कहता है कि वह connected है, पर यूज़र को hotel या public Wi‑Fi पेज पर "Accept" दबाना होता है। WorkManager इस स्थिति का विश्वसनीय पता नहीं लगा सकता। इसे सामान्य failure मानें: सिंक करें, जल्दी timeout करें, और बाद में retry करें। साथ ही एक सरल इन‑ऐप संदेश रखें जैसे "Wi‑Fi से जुड़ा है लेकिन इंटरनेट नहीं" जब आप इसे request के दौरान पहचान पाएं।

छोटे बनाम बड़े अपलोड्स के लिए अलग constraints इस्तेमाल करें ताकि ऐप responsive रहे:

  • छोटे payloads (status pings, form metadata): किसी भी नेटवर्क पर, बैटरी न लो।
  • बड़े payloads (photos, videos, map packs): संभव हो तो unmetered नेटवर्क और चार्जिंग पर विचार करें।

उदाहरण: एक तकनीशियन 2 फोटो के साथ एक फॉर्म सेव करता है। फॉर्म फील्ड्स किसी भी कनेक्शन पर सबमिट कर दें, पर फोटो अपलोड्स को Wi‑Fi या बेहतर मौके का इंतज़ार कराएं। ऑफिस जल्दी जॉब देख सकेगा, और डिवाइस मोबाइल डाटा बैकग्राउंड में images अपलोड कर के खर्च नहीं करेगा।

उपयोगकर्ता को परेशान न करने वाले exponential backoff के साथ retries

Give ops a clear admin view
Add a web admin panel for ops teams so they can see what synced, what failed, and why.
Build Admin

Retries वे जगह हैं जहाँ फील्ड ऐप्स शांत या टूटे हुए महसूस करते हैं। उस तरह का backoff चुनें जो आपको उम्मीद है कि तरह के फेल्यर के लिए उपयुक्त हो।

नेटवर्क्स के लिए exponential backoff आमतौर पर सबसे सुरक्षित डिफॉल्ट है। यह जल्दी वेट बढ़ाता है ताकि आप कनेक्टिविटी खराब होने पर सर्वर पर hammer न करें या बैटरी न जलाएँ। Linear backoff अल्पकालिक समस्याओं (जैसे flaky VPN) के लिए फिट हो सकता है, पर कमजोर सिग्नल क्षेत्रों में यह बहुत बार retry कर सकता है।

फेल्यर टाइप के आधार पर retry निर्णय लें, न कि सिर्फ़ "कुछ फेल हुआ" के आधार पर। एक सरल नियम सेट मदद करता है:

  • नेटवर्क timeout, 5xx, DNS, कोई connectivity नहीं: Result.retry()
  • Auth expired (401): एक बार token refresh करें, फिर fail करके यूज़र से साइन-इन मांगे
  • Validation या 4xx (bad request): Result.failure() और सपोर्ट के लिए स्पष्ट त्रुटि
  • Conflict (409) जैसे पहले से भेजे गए आइटम: अगर आपका sync idempotent है तो इसे success मानें

किसी स्थायी त्रुटि को अनंत काल तक लूप न करने दें। अधिकतम प्रयासों की गिनती सेट करें, और उसके बाद रुककर एक शांत, actionable संदेश दिखाएँ (बार-बार नोटिफिकेशन न भेजें)।

आप attempts बढ़ने पर व्यवहार भी बदल सकते हैं। उदाहरण के लिए, 2 असफलताओं के बाद छोटे बैच भेजें या बड़े अपलोड्स को अगले सफल pull तक छोड़ दें।

val request = OneTimeWorkRequestBuilder\u003cSyncWorker\u003e()
  .setBackoffCriteria(
    BackoffPolicy.EXPONENTIAL,
    30, TimeUnit.SECONDS
  )
  .build()

// in doWork()
if (runAttemptCount \u003e= 5) return Result.failure()
return Result.retry()

यह retries को शिष्ट बनाता है: कम wakeups, कम यूज़र इंटरप्शन, और जब कनेक्शन वापस आए तो तेज़ रिकवरी।

यूज़र-देखने योग्य प्रगति: नोटिफिकेशन, foreground work, और स्टेटस

Run a small field pilot
Ship one reliable slice first, like an inspection form with photos and safe retries.
Start a Pilot

फील्ड ऐप्स अक्सर तब सिंक करते हैं जब यूज़र की उम्मीद नहीं होती: बेसमेंट में, धीमे नेटवर्क पर, या लगभग ख़त्म बैटरी के साथ। अगर सिंक उस चीज़ को प्रभावित करता है जिसका यूज़र इंतज़ार कर रहा है (uploads, reports, photo batches), तो इसे दिखाई और समझने में आसान बनाएं। छोटे, तेज़ अपडेट्स के लिए साइलेंट बैकग्राउंड वर्क अच्छा है। कुछ भी लंबा होने पर ईमानदार महसूस होना चाहिए।

कब foreground work जरूरी है

जब कोई जॉब लंबा चला, समय-संवेदी हो, या स्पष्ट रूप से यूज़र क्रिया से जुड़ा हो तो foreground execution का उपयोग करें। आधुनिक Android पर बड़े अपलोड्स तब तक रोके या delay किए जा सकते हैं जब तक आप foreground न चलाएं। WorkManager में इसका मतलब है कि ForegroundInfo लौटाएं ताकि सिस्टम ongoing notification दिखा सके।

एक अच्छा नोटिफिकेशन तीन प्रश्नों का जवाब देता है: क्या सिंक हो रहा है, कितना पूरा हुआ है, और इसे कैसे रोकें। एक साफ़ cancel action जोड़ें ताकि यूज़र meter किए गए डेटा पर हो तो वापस हट सके या अपने फोन की ज़रूरत हो तो रोक सके।

प्रगति जिसपर लोग भरोसा कर सकें

प्रगति असली यूनिट्स से मेल खाना चाहिए, धुंधली प्रतिशतों से नहीं। setProgress के साथ प्रगति अपडेट करें और UI में (या स्टेटस स्क्रीन पर) WorkInfo से पढ़ें।

अगर आप 12 फोटो और 3 फॉर्म अपलोड कर रहे हैं तो दिखाएं “5 of 15 items uploaded”, जो बचा है वो दिखाएँ, और सपोर्ट के लिए आखिरी error संदेश रखें।

प्रगति को अर्थपूर्ण रखें:

  • हुए आइटम और बचे आइटम
  • वर्तमान चरण ("Uploading photos", "Sending forms", "Finalizing")
  • आखिरी सफल सिंक समय
  • आखिरी त्रुटि (संक्षिप्त, यूज़र-फ्रेंडली)
  • स्पष्ट cancel/stop विकल्प

अगर आपकी टीम तेज़ी से इन-हाउस टूल AppMaster से बनाती है, तो वही नियम रखें: यूज़र तब सिंक पर भरोसा करते हैं जब वे इसे देख सकें और जब वह वास्तव में उनके काम से मेल खाता हो।

Unique work, tags, और duplicate sync jobs से बचाव

Duplicate sync jobs बैटरी और मोबाइल डाटा दोनों जला सकते हैं और सर्वर-साइड conflicts पैदा कर सकते हैं। WorkManager दो साधारण टूल देता है: unique work names और tags।

एक अच्छा default यह है कि "sync" को एक ही lane मानें। हर बार नया job enqueue करने के बजाय वही unique work name enqueue करें। इस तरह, जब यूज़र ऐप खोलता है, नेटवर्क बदलता है, और periodic job एक ही समय पर ट्रिगर होता है तो sync storm नहीं बनता।

val request = OneTimeWorkRequestBuilder\u003cSyncWorker\u003e()
  .addTag("sync")
  .build()

WorkManager.getInstance(context)
  .enqueueUniqueWork("sync", ExistingWorkPolicy.KEEP, request)

नीतियों का चुनाव मुख्य व्यवहार विकल्प है:

  • KEEP: अगर एक sync पहले से चल रहा (या queued) है तो नए request को ignore करें। अधिकांश "Sync now" बटन और auto-sync triggers के लिए यह उपयोगी है।
  • REPLACE: वर्तमान को cancel कर नया शुरू करें। यह तब उपयोगी है जब inputs सचमुच बदल गए हों, जैसे यूज़र ने अकाउंट बदला या प्रोजेक्ट चुना।

Tags नियंत्रण और visibility के लिए उपयोगी हैं। एक स्थिर tag जैसे sync से आप cancel कर सकते हैं, status query कर सकते हैं, या logs filter कर सकते हैं बिना specific IDs ट्रैक किए। यह manual “sync now” के लिए खासकर उपयोगी है: आप देख सकते हैं कि क्या पहले से काम चल रहा है और फिर एक स्पष्ट संदेश दिखा सकते हैं बजाय नए worker लॉन्च करने के।

Periodic और on-demand sync को लड़ने नहीं देना चाहिए। उन्हें अलग पर समन्वित रखें:

  • scheduled job के लिए enqueueUniquePeriodicWork("sync_periodic", KEEP, ...) का उपयोग करें।
  • on-demand के लिए enqueueUniqueWork("sync", KEEP, ...) का उपयोग करें।
  • worker में जल्दी exit करें अगर कुछ अपलोड/डाउनलोड करने को न हो, ताकि periodic run सस्ता रहे।
  • वैकल्पिक रूप से, periodic worker वही one-time unique sync enqueue कर सकता है ताकि सारा असली काम एक ही जगह हो।

ये पैटर्न background sync को predictable बनाते हैं: एक समय में एक ही sync, cancel करना आसान, और देखना आसान।

कदम-दर-कदम: एक व्यावहारिक बैकग्राउंड सिंक पाइपलाइन

Keep full control with source
Get real source code output so your team can review, extend, and self-host when needed.
Generate Code

एक भरोसेमंद सिंक पाइपलाइन बनाने में सरल state machine की तरह सोचने पर आसानी होती है: वर्क आइटम पहले लोकली रहते हैं, और WorkManager केवल तब उन्हें आगे बढ़ाता है जब स्थितियाँ ठीक हों।

एक सरल पाइपलाइन जो आप भेज सकते हैं

  1. लोकल “queue” टेबल्स से शुरू करें। resume करने के लिए जो सबसे छोटा मेटाडेटा चाहिए वह स्टोर करें: item id, type (form, photo, note), status (pending, uploading, done), attempts count, last error, और downloads के लिए cursor या server revision।

  2. यूज़र-टैप्ड "Sync now" के लिए, constraints के साथ OneTimeWorkRequest enqueue करें जो आपके वास्तविक दुनिया से मेल खाती हों। आम चयन हैं: network connected और battery not low। अगर uploads भारी हों तो charging भी मांगें।

  3. एक CoroutineWorker लागू करें जिसमें स्पष्ट फेज़ हों: upload, download, reconcile। हर फेज़ incremental रखें। केवल pending आइटम upload करें, केवल last cursor से हुए बदलाव download करें, फिर conflicts को सरल नियमों से reconcile करें (उदा.: assignment fields में server जीतता है, local draft notes client जीतते हैं)।

  4. backoff के साथ retries जोड़ें, पर सिर्फ़ उन्हीं चीज़ों को retry करें जो अस्थायी हैं। Timeouts और 500s retry होने चाहिए। 401 (logged out) पर fast fail करके UI को बताएं क्या हुआ।

  5. UI और नोटिफिकेशन्स चलाने के लिए WorkInfo observe करें। "Uploading 3 of 10" जैसे फेज़ के लिए progress updates का उपयोग करें, और एक छोटा failure संदेश दिखाएँ जो अगले कदम को बताता हो (retry, sign in, Wi‑Fi से connect)।

val constraints = Constraints.Builder()
  .setRequiredNetworkType(NetworkType.CONNECTED)
  .setRequiresBatteryNotLow(true)
  .build()

val request = OneTimeWorkRequestBuilder\u003cSyncWorker\u003e()
  .setConstraints(constraints)
  .setBackoffCriteria(BackoffPolicy.EXPONENTIAL, 30, TimeUnit.SECONDS)
  .build()

जब आप queue लोकल रखें और worker फेज़ स्पष्ट रखें, तो आपको predictable व्यवहार मिलता है: काम pause हो सकता है, resume कर सकता है, और यूज़र को बिना अनुमान के बता सकता है कि क्या हुआ।

आम गलतियाँ और जाल (और उन्हें कैसे टालें)

भरोसेमंद सिंक अक्सर कुछ छोटे चुनावों की वजह से फेल होता है जो डेवलप में निर्दोष दिखते हैं पर असली डिवाइसेज़ पर टूट जाते हैं। लक्ष्य यह नहीं है कि जितना हो सके उतना बार रन किया जाए—बल्कि सही समय पर सही काम करना और जब नहीं कर सकते तो साफ़ तरीके से रुकना है।

नजर रखने वाले जाल

  • बिना constraints के बड़े अपलोड करना। अगर आप photos या बड़े payloads किसी भी नेटवर्क और किसी भी बैटरी स्तर पर भेजेंगे तो उपयोगकर्ता महसूस करेगा। नेटवर्क प्रकार और low battery के लिए constraints जोड़ें, और बड़े काम को छोटे हिस्सों में बांटें।
  • हर फेल्यर पर अनंत retries। 401, expired token, या missing permission अस्थायी नहीं होते। इन्हें hard failure बनाएं, एक स्पष्ट क्रिया surface करें (re-login), और सिर्फ़ अस्थायी मुद्दों को ही retry करें।
  • गलती से duplicates बनाना। अगर worker दो बार चल सकता है तो सर्वर double creates देखेगा जब तक requests idempotent न हों। हर आइटम पर client-generated stable ID इस्तेमाल करें और सर्वर को repeats को update समझने दें।
  • near-real-time जरूरतों के लिए periodic work का उपयोग। Periodic work रखरखाव के लिए अच्छा है, न कि "sync now" के लिए। यूज़र-इनीशिएटेड सिंक के लिए one-time unique work enqueue करें।
  • "100%" बहुत जल्दी रिपोर्ट करना। अपलोड पूरा होना और डेटा स्वीकार कर लेना अलग हैं। progress को stages (queued, uploading, server confirmed) से ट्रैक करें और तभी done दिखाएँ जब सर्वर कन्फर्म करे।

एक ठोस उदाहरण: तकनीशियन तीन फोटो के साथ एक फॉर्म elevator में कमजोर सिग्नल पर सबमिट करता है। अगर आप बिना constraints तुरंत शुरू कर दें तो uploads अटके रह जाएंगे, retries spike करेंगे, और ऐप restart पर फॉर्म दो बार बन सकता है। अगर आप usable network पर constraint रखें, काम चरणों में करें, और हर फॉर्म का stable ID रखें तो वही सीन साफ़ सर्वर रिकॉर्ड और सटीक प्रगति संदेश के साथ खत्म होगा।

भेजने से पहले त्वरित चेकलिस्ट

Generate a backend that scales
Generate a production-ready backend in Go and keep your sync contract stable as requirements change.
Create Backend

रिलीज़ से पहले, असली फील्ड यूज़र्स की तरह sync टेस्ट करें: बिंदु-स्तरीय सिग्नल, मृत बैटरी, और बहुत टैप करना। जो कुछ डेवलपर फोन पर ठीक दिखता है, वह scheduling, retries, या status reporting गलत होने पर फिर भी फील्ड में फेल हो सकता है।

इन चेक्स को कम से कम एक पुराने/धीमे डिवाइस और एक नए डिवाइस पर चलाएँ। लॉग रखें, पर साथ ही UI में यूज़र क्या देखता है यह ध्यान से देखें।

  • कोई नेटवर्क नहीं, फिर रिकवरी: connectivity बंद करके sync शुरू करें, फिर वापस ऑन करें। पुष्टि करें कि काम queued है (न कि fail होकर खत्म), और बिना duplicates के बाद में resume हो जाता है।
  • डिवाइस रिस्टार्ट: सिंक शुरू करें, बीच में रिबूट करें, फिर ऐप खोलें। जांचें कि काम जारी रहता है या सही से re-schedule होता है, और ऐप सही वर्तमान स्थिति दिखाता है ("syncing" पर अटका नहीं)।
  • कम बैटरी और कम स्टोरेज: battery saver चालू करें, low-battery थ्रेशोल्ड के नीचे ले जाएँ अगर संभव हो, और स्टोरेज को भरकर करीब पूरा करें। पुष्टि करें कि job तब रुके जब उसे रुकना चाहिए, और हालत बेहतर होने पर बिना retry-loop के जारी हो।
  • बार-बार ट्रिगर: अपने "Sync" बटन को कई बार टैप करें या कई स्क्रीन से sync ट्रिगर करें। फिर भी एक तार्किक sync रन होना चाहिए, न कि कई parallel workers जो एक ही रिकॉर्ड के लिए compete कर रहे हों।
  • सर्वर फेल्यर्स जिन्हें आप समझा सकें: 500s, timeouts, और auth errors की नकल करें। जाँचें कि retries back off करते हैं और cap के बाद रुकते हैं, और यूज़र को एक स्पष्ट संदेश दिखे जैसे "Can’t reach server, will retry" न कि एक generic failure।

अगर कोई भी टेस्ट ऐप को अस्पष्ट स्थिति में छोड़ देता है, तो उसे बग मानें। यूज़र धीमी sync को माफ़ करते हैं, पर वे डाटा खोने या नहीं जानने कि क्या हुआ उसे माफ़ नहीं करते।

उदाहरण परिदृश्य: ऑफ़लाइन फॉर्म्स और फील्ड ऐप में फोटो अपलोड

One platform for the whole stack
Build the backend, web app, and native mobile apps together so models and auth stay aligned.
Create App

एक तकनीशियन कमजोर कवरेज वाले साइट पर पहुँचता है। वे ऑफ़लाइन एक सर्विस फॉर्म भरते हैं, 12 फोटो लेते हैं, और Submit दबाकर चले जाते हैं। ऐप पहले सब कुछ लोकली सेव कर लेता है (उदा. लोकल डेटाबेस में): फॉर्म के लिए एक रिकॉर्ड, और हर फोटो के लिए एक रिकॉर्ड जिसमें स्पष्ट स्टेट हो जैसे PENDING, UPLOADING, DONE, या FAILED

जब वे Submit दबाते हैं, ऐप एक unique sync job enqueue करता है ताकि अगर वे दो बार टैप कर दें तो duplicate न बने। आम सेटअप WorkManager chain है जो पहले photos अपलोड करता है (बड़े, धीमे), फिर attachments कन्फर्म होने के बाद फॉर्म payload भेजता है।

सिंक केवल तब चलता है जब स्थितियाँ वास्तविक जीवन से मेल खाती हों। उदाहरण के लिए, यह connected network, non-low battery, और पर्याप्त storage का इंतज़ार कर सकता है। अगर टेक अभी भी बेसमेंट में है और सिग्नल नहीं है तो कुछ भी बैकग्राउंड में बार-बार कोशिश करके बैटरी नहीं जलाएगा।

प्रगति स्पष्ट और यूज़र-फ्रेंडली होती है। अपलोड foreground work के रूप में चलता है और एक नोटिफिकेशन दिखाता है जैसे “Uploading 3 of 12”, साथ में साफ़ Cancel action। अगर वे cancel करते हैं तो ऐप वर्क रोक देता है और बाकी आइटमों को PENDING में रखता है ताकि वे बाद में बिना डाटा खोए retry कर सकें।

फ़्लेकी hotspot के बाद retries शिष्ट तरीके से व्यवहार करते हैं: पहली विफलता जल्दी retry करती है, पर हर विफलता के बाद इंतज़ार लंबा होता है (exponential backoff)। शुरू में यह उत्तरदायी लगता है, फिर बैटरी और नेटवर्क स्पैम से बचने के लिए पीछे हटता है।

ऑप्स टीम के लिए फायदा व्यावहारिक है: कम duplicate submissions क्योंकि आइटम idempotent और uniquely queued हैं, स्पष्ट failure states (कौन-सी फोटो फेल हुई, क्यों, और कब retry होगी), और बेहतर भरोसा कि “submitted” का मतलब है “सुरक्षित रूप से स्टोर हुआ और sync होगा”।

अगले कदम: पहले reliability भेजें, फिर sync स्कोप बढ़ाएँ

अधिक sync फीचर जोड़ने से पहले यह स्पष्ट करें कि "done" का क्या मतलब है। अधिकांश फील्ड ऐप्स के लिए यह "request भेजा गया" नहीं है। यह है "सर्वर ने स्वीकार किया और कन्फर्म किया", साथ में UI स्टेट जो वास्तविकता से मेल खाती हो। एक फॉर्म जो "Synced" कहता है, उसे ऐप restart के बाद भी वैसा ही रहना चाहिए, और एक फॉर्म जो फेल हुआ उसे अगली क्रिया स्पष्ट दिखानी चाहिए।

यूज़र को भरोसा देने के लिए कुछ छोटे संकेत जोड़ें जिन्हें लोग और सपोर्ट दोनों देख सकें। इन्हें सरल और स्क्रीन-दर-स्क्रीन सुसंगत रखें:

  • आखिरी सफल सिंक समय
  • आखिरी सिंक त्रुटि (संक्षिप्त संदेश)
  • पेंडिंग आइटम (उदाहरण: 3 forms, 12 photos)
  • वर्तमान सिंक स्थिति (Idle, Syncing, Needs attention)

Observability को फीचर का हिस्सा मानें। जब कोई कमजोर कनेक्शन में हो और नहीं जानता कि ऐप काम कर रहा है या नहीं, तो यह कई घंटे बचाता है।

अगर आप backend और admin tools भी बना रहे हैं तो उन्हें साथ में जनरेट करना sync contract को स्थिर रखने में मदद करता है। AppMaster (appmaster.io) production-ready backend, web admin panel, और native mobile apps जनरेट कर सकता है, जिससे आप मॉडल और auth को aligned रखकर tricky sync एजेस पर ध्यान लगा सकें।

अंत में, एक छोटा पायलट चलाएँ। एक end-to-end sync slice चुनें (उदा. “inspection form with 1-2 photos”) और उसे constraints, retries, और यूज़र-देखने योग्य प्रगति के साथ काम करते हुए भेजें। जब वह slice boring और predictable लगे, तब एक-एक करके फीचर बढ़ाएँ।

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

What does “reliable background sync” actually mean in a field app?

Reliable background sync का मतलब है कि डिवाइस पर बनाया गया काम पहले लोकली सुरक्षित हो और बाद में यूज़र को दोहराना न पड़े ऐसे अपलोड हो जाए। इसे ऐप के क्रैश होने, रिबूट, कमजोर नेटवर्क और retries के बावजूद डाटा खोए बिना या डुप्लीकेट बनाए बिना पूरा होना चाहिए।

When should I use OneTimeWorkRequest vs PeriodicWorkRequest for sync?

इवेंट-टिगर किए गए काम (जैसे “फॉर्म सेव हुआ”, फोटो जोड़ा गया, या यूज़र ने Sync दबाया) के लिए OneTimeWorkRequest का इस्तेमाल करें।

सिर्फ़ रखरखाव के लिए और एक सुरक्षा जाल के रूप में PeriodicWorkRequest उपयोग करें, पर महत्वपूर्ण अपलोड्स का अकेला रास्ता नहीं बनाना चाहिए क्योंकि इसका समय स्थानांतरित हो सकता है।

Which Worker type should I choose: Worker, CoroutineWorker, or RxWorker?

Kotlin में और जब आपके कोड में suspend फंक्शन हों तो CoroutineWorker सबसे अच्छा विकल्प है—कोड संक्षिप्त रहता है और cancellation वर्क जैसा उम्मीद के मुताबिक होता है।

Worker छोटे ब्लॉकिंग टास्क के लिए ठीक है। RxWorker तभी उपयोगी है जब ऐप पहले से RxJava पर बना हो।

Should I chain multiple workers or do everything in one worker?

स्टेप्स अलग constraints चाहते हों या अलग-अलग retry लॉजिक हो तो workers को chain करें—उदा. Wi‑Fi पर बड़ा अपलोड, फिर किसी भी नेटवर्क पर हल्का API कॉल।

अगर स्टेप्स एक ही ट्रांज़ैक्शन जैसा व्यवहार करें और साझा स्टेट चाहिए, तो एक ही worker में स्पष्ट फ़ेज़ रखें—यह “all-or-nothing” व्यवहार देता है।

How do I stop retries from creating duplicate records on the server?

हर create/update रिक्वेस्ट को दो बार चलाने पर भी सुरक्षित बनाएं: हर आइटम के लिए एक idempotency key (अक्सर UUID) रखें जिसे सर्वर उसी अनुरोध के रूप में समझे।

अगर सर्वर बदला नहीं जा सकता तो stable natural key, upsert endpoint या version नंबर का उपयोग करें ताकि stale अपडेट reject हो सकें।

How do I make uploads resumable if the app is killed mid-sync?

लोकल स्टेट्स जैसे queued, uploading, uploaded, failed रखें ताकि worker क्रैश के बाद भी बिना अनुमान लगाये resume कर सके।

फ़ाइल URI और अपलोड मेटाडेटा को persist करें और तभी uploaded मार्क करें जब सर्वर कन्फर्म कर दे—इससे restart पर वही काम फिर से शुरू नहीं होगा।

What constraints are a good default for field app sync jobs?

फील्ड ऐप्स के लिए सामान्य डिफॉल्ट constraints: नेटवर्क कनेक्शन जरूरी हो, बैटरी लो होने पर चलाना टालें, और स्टोरेज critically low होने पर न चलाएँ।

"Charging" या "Unmetered" तभी जोड़ें जब काम भारी हो और समय-समवेदनशील न हो—वरना बहुत ज़्यादा constraints होने से sync कभी नहीं चलेगा।

How should my app handle captive portals or “Wi‑Fi with no internet”?

"Connected but no internet" को सामान्य failure की तरह ट्रीट करें: जल्दी timeout करें, Result.retry() लौटाएँ और बाद में फिर कोशिश करें।

यदि आप यह परिस्थितियाँ request के दौरान पहचान पाते हैं तो UI में सरल संदेश दिखाएँ जैसे “Wi‑Fi से जुड़ा है लेकिन इंटरनेट नहीं” ताकि यूज़र समझ सके।

What’s the safest retry strategy for spotty networks?

बड़ी-बड़ी नेटवर्क फेल्यर्स के लिए exponential backoff सबसे सुरक्षित डिफॉल्ट है—यह जल्दी से वेट बढ़ाता है ताकि आप सर्वर या बैटरी पर दबाव न डालें।

नेटवर्क timeouts और 5xx के लिए retry करें; 401 जैसी auth expired स्थितियों पर टोकन एक बार रीफ़्रेश करें और फिर failure दिखाएँ; 4xx validation एरर पर Result.failure() और सपोर्ट के लिए साफ़ मैसेज दें।

कुल retry attempts की सीमा रखें ताकि permanent error में infinite loop न बने।

How do I prevent “sync storms” and still show user-visible progress?

sync storms से बचने के लिए unique work नाम और tags का इस्तेमाल करें—इससे एक ही तरह के कई समान jobs एक साथ नहीं चलेंगे।

उदाहरण: on-demand और periodic दोनों अलग रखें और on-demand के लिए enqueueUniqueWork("sync", KEEP, ...) इस्तेमाल करें। लंबा काम हो तो foreground work के साथ ongoing notification दिखाएँ और उपयोगकर्ता को cancel करने का ऑप्शन दें।

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

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

शुरू हो जाओ