ফিল্ড অ্যাপের জন্য Kotlin WorkManager ব্যাকগ্রাউন্ড সিঙ্ক প্যাটার্ন
Kotlin WorkManager ব্যাকগ্রাউন্ড সিঙ্ক প্যাটার্ন ফিল্ড অ্যাপগুলোর জন্য: সঠিক কাজ টাইপ বেছে নিন, কনস্ট্রেইনট সেট করুন, এক্সপোনেনশিয়াল ব্যাকঅফ ব্যবহার করুন, এবং ব্যবহারকারী-দৃশ্যমান প্রগ্রেস দেখান।

ফিল্ড ও অপস অ্যাপগুলিতে নির্ভরযোগ্য ব্যাকগ্রাউন্ড সিঙ্ক কী মানে\n\nফিল্ড ও অপস অ্যাপগুলিতে, সিঙ্ক একটা “ভালো হবে” টুইক নয়—এটা হলো কিভাবে কাজ ডিভাইস থেকে বেরিয়ে টিমের জন্য বাস্তব হয়। যখন সিঙ্ক ব্যর্থ হয়, ব্যবহারকারী দ্রুত দেখবে: একটি সম্পন্ন কাজ এখনও "pending" দেখায়, ছবি হারিয়ে যায়, অথবা একই রিপোর্টটি দুইবার আপলোড হয়ে ডুপ্লিকেট তৈরি করে।\n\nএমন অ্যাপগুলো সাধারণ কনজিউমার অ্যাপের তুলনায় কঠিন কারণ ফোনগুলো সবচেয়ে খারাপ পরিস্থিতিতেই কাজ করে। নেটওয়ার্ক লাতা-লাটি করে LTE, দুর্বল Wi‑Fi এবং কোন সিগনাল না—সমস্ত অবস্থায় যায়। ব্যাটারি সেভার ব্যাকগ্রাউন্ড কাজ ব্লক করে। অ্যাপKilled হয়, OS আপডেট হয়, ডিভাইস রুট অবস্থায় রিবুট করে। একটি নির্ভরযোগ্য WorkManager সেটআপকে এসব ছাড়া বেঁচে থাকতে হবে।\n\nনির্ভরযোগ্য বলতে সাধারণত চারটি জিনিস বোঝায়:\n\n- অবশেষে সামঞ্জস্যপূর্ণ: ডেটা দেরিতে আসতে পারে, কিন্তু ম্যানুয়াল নজরদারি ছাড়াই এসে যায়।\n- পুনরুদ্ধারযোগ্য: যদি অ্যাপ আপলোডের মাঝখানে মারা যায়, পরবর্তী রান নিরাপদে চালিয়ে যায়।\n- দৃশ্যমান: ব্যবহারকারী ও সাপোর্ট কিভাবে হচ্ছে এবং কী আটকেছে তা জানতে পারে।\n- ধ্বংসাত্মক নয়: রিট্রাই ডুপ্লিকেট সৃষ্টি না করে বা স্টেট করাপ্ট না করে।\n\n“Run now” ছোট, ব্যবহারকারী-ট্রিগার করা অ্যাকশনের জন্য ভাল (যেমন ব্যবহারকারী একটি স্ট্যাটাস আপডেট পাঠিয়ে ফেলে)। “Wait” ভারী কাজের জন্য—ফটো আপলোড, ব্যাচ আপডেট বা এমন কিছু যা ব্যাটারি খায় বা খারাপ নেটে ব্যর্থ হওয়ার সম্ভাবনা বেশি।\n\nউদাহরণ: একজন ইন্সপেক্টর বেইসমেন্টে সিগনাল না থাকায় 12টি ফটো সহ একটি ফর্ম জমা দেয়। একটি নির্ভরযোগ্য সিঙ্ক প্রথমে সবকিছু লোকালি সংরক্ষণ করে, কিউজ হিসেবে চিহ্নিত করে, এবং পরে ডিভাইসে ভালো কানেকশন পেলে আপলোড করে—ইন্সপেক্টরকে কাজ পুনরায় করতে হবে না।\n\n## সঠিক WorkManager বিল্ডিং ব্লক বেছে নিন\n\nছোট ও স্পষ্ট কাজের ইউনিট বেছে নিন—এই সিদ্ধান্তটা পরে যে কোনও বুদ্ধিমান রিট্রাই লজিকের চেয়ে বেশি প্রভাব ফেলে।\n\n### One-time বনাম periodic work\n\nযেসব কাজ ঘটবে যখন কিছু বদলে যায় (নতুন ফর্ম সেভ, ছবি কম্প্রেস শেষ, ব্যবহারকারী Sync চাপল), সেগুলোর জন্য OneTimeWorkRequest ব্যবহার করুন। তাৎক্ষণিকভাবে enqueue করুন (কনস্ট্রেইনট সহ) এবং WorkManager যেদিন ডিভাইস প্রস্তুত হবে চালিয়ে দেবে।\n\nস্টেডি মেইনটেন্যান্সের জন্য, যেমন “আপডেট চেক” বা নৈটলি ক্লিনআপ, PeriodicWorkRequest ব্যবহার করুন। Periodic work সঠিক নয়—এর একটি ন্যূনতম ইন্টারভাল থাকে এবং ব্যাটারি ও সিস্টেম নিয়মের ওপর ভিত্তি করে এটি ড্রিফ্ট করতে পারে, তাই এটি গুরুত্বপূর্ণ আপলোডের একমাত্র পথ হওয়া উচিত নয়।\n\nপ্রায়োগিক প্যাটার্ন হলো: “মোস্ট-সিংক-সুন” জন্য one-time work, এবং periodic work কে এক ধরনের সেফটি নেট হিসেবে রাখা।\n\n### Worker, CoroutineWorker, না RxWorker বেছে নেওয়া\n\nআপনি যদি Kotlin লিখেন এবং suspend ফাংশন ব্যবহার করেন, তাহলে CoroutineWorker পছন্দ করুন। এটি কোড ছোট রাখে এবং ক্যানসেলেশন প্রত্যাশিতভাবে কাজ করে।\n\nWorker সরল ব্লকিং কোডের জন্য উপযুক্ত, কিন্তু খুব বেশি ব্লক করলে সমস্যা হবে।\n\nRxWorker কেবল তখনই যুক্তিযুক্ত যদি আপনার অ্যাপ ইতিমধ্যেই RxJava বড় পরিসরে ব্যবহার করে—অন্যথায় এটা অপ্রয়োজনীয় জটিলতা।\n\n### ধাপগুলো চেইন করবেন নাকি এক worker-এ ফেজ করবেন?\n\nচেইনিং ভালো যখন ধাপগুলো স্বাধীনভাবে সফল বা ব্যর্থ হতে পারে এবং আলাদা রিট্রাই ও পরিষ্কার লগ চান। এক worker-এ ফেজ ভালো যখন ধাপগুলো ডেটা শেয়ার করে এবং একটিই ট্রানজেকশনের মতো বিবেচিত হওয়া উচিত।\n\nএকটি সহজ নিয়ম:\n\n- যখন ধাপগুলোর কনস্ট্রেইনট আলাদা (Wi‑Fi-এ আপলোড, তারপর লাইটওয়েট API কল), তখন চেইন করুন।\n- যখন আপনি একটি “all-or-nothing” সিঙ্ক চান, তখন এক worker ব্যবহার করুন।\n\nWorkManager গ্যারান্টি দেয় যে কাজ প্রসেস ডেথ ও রিবুট সহ সংরক্ষিত থাকবে এবং কনস্ট্রেইনট মানবে। এটি তৎক্ষণিক এক্সিকিউশন বা ইউজার ফোর্স-স্টপের পরে চলার নিশ্চয়তা দেয় না। যদি আপনি Android ফিল্ড অ্যাপ তৈরি করেন, সিঙ্ক ডিজাইন করুন যাতে দেরি নিরাপদ ও প্রত্যাশিত হয়।\n\n## সিঙ্ক নিরাপদ করুন: idempotent, ধাপে ধাপে এবং resumable রাখুন\n\nফিল্ড অ্যাপ পুনরায় কাজ চালাবে—ফোন সিগনাল হারায়, OS প্রসেস মেরে ফেলে, এবং ব্যবহারকারী দুইবার ট্যাপ করবে কারণ কিছুই ঘটেনি মনে হয়। যদি ব্যাকগ্রাউন্ড সিঙ্ক পুনরাবৃত্তির জন্য নিরাপদ না হয়, তাহলে ডুপ্লিকেট রেকর্ড, মিসিং আপডেট বা অনন্ত রিট্রাই হবে।\n\nপ্রত্যেক সার্ভার কল দুইবার চালানো নিরাপদ করে শুরু করুন। সহজ পদ্ধতি হলো প্রতিটি আইটেমের জন্য একটি idempotency key (উদাহরণ: লোকাল রেকর্ডের সাথে সংরক্ষিত UUID) যা সার্ভার “একই অনুরোধ, একই ফলাফল” হিসেবে বিবেচনা করে। সার্ভার বদলাতে না পারলে stable natural key ও upsert endpoint ব্যবহার করুন, অথবা ভার্সন নম্বর দিন যাতে সার্ভার স্টেল আপডেট প্রত্যাখ্যান করতে পারে।\n\nলোকাল স্টেট স্পষ্টভাবে ট্র্যাক করুন যাতে worker ক্র্যাশের পরে অনুমান না করে পুনরায় চলতে পারে। একটি সাধারণ স্টেট মেশিন প্রায়ই যথেষ্ট:\n\n- queued\n- uploading\n- uploaded\n- needs-review\n- failed-temporary\n\nইনক্রিমেন্টাল সিঙ্ক রাখুন। “সবকিছু সিঙ্ক করুন” বলার বদলে lastSuccessfulTimestamp বা সার্ভার-ইস্যুকৃত টোকেনের মতো একটি কার্সর সংরক্ষণ করুন। ছোট ব্যাচে পরিবর্তন পড়ুন, প্রয়োগ করুন, তারপর ব্যাচ সম্পূর্ণভাবে লোকালি কমিট হলে কার্সর অ্যাডভান্স করুন। ছোট ব্যাচ (২০–১০০ আইটেম) টাইমআউট কমায়, প্রগ্রেস দৃশ্যমান করে এবং বিরতি হলে যে কাজ পুনরাবৃত্তি হবে তা সীমাবদ্ধ রাখে।\n\nআপলোডগুলোও resumable করুন। ফটো বা বড় পে-লোডের জন্য ফাইল URI এবং আপলোড মেটাডেটা অক্ষুণ্ণ রাখুন, এবং সার্ভার কনফার্ম না করলে uploaded মার্ক করবেন না। worker পুনরায় শুরু হলে শেষ জানা স্টেট থেকে চালিয়ে দেয়, নতুন করে শুরু করে না।\n\nউদাহরণ: একজন টেকনিশিয়ান ভাল সংযোগ না থাকলে 12টি ফর্ম পূরণ করে এবং 8টি ফটো জোড়ে। ডিভাইস পুনরায় সংযুক্ত হলে worker ব্যাচে আপলোড করে, প্রতিটি ফর্মের idempotency key থাকে, এবং প্রতিটি ব্যাচ সফল হলে কার্সর অ্যাডভান্স করে। অ্যাপ মাঝপথে মারা গেলে worker পুনরায় চালালে বাকি কিউ করা আইটেমগুলো সম্পন্ন হবে কোন কিছু ডুপ্লিকেট না করে।\n\n## বাস্তব-জগতের ডিভাইস কনডিশন মেলানো কনস্ট্রেইনটস\n\nকনস্ট্রেইনট হলো সেই গার্ডরেইল যা ব্যাকগ্রাউন্ড সিঙ্ককে ব্যাটারি শুকানো, মোবাইল ডেটা বাড়ানো বা সবচেয়ে ভুল সময়ে ব্যর্থ হওয়া থেকে রক্ষা করে। আপনি এমন কনস্ট্রেইনট চান যা ফিল্ডে ডিভাইস কীভাবে আচরণ করে তা প্রতিফলিত করে—আপনার ডেস্কে না কেমন আচরণ করে তা নয়।\n\nছোট একটি সেট দিয়ে শুরু করুন যা ব্যবহারকারীকে রক্ষা করে কিন্তু কাজটি অধিকাংশ দিনে চলার সুযোগ রাখে। একটি প্রায়োগিক বেসলাইন: নেটওয়ার্ক থাকা প্রয়োজন, ব্যাটারি লো হলে চালাবেন না, এবং স্টোরেজ ক্রিটিক্যাল হলে চালাবেন না। শুধু খুব ভারী কাজ হলে চার্জিং দরকার বলুন—কিন্তু অনেক ফিল্ড ডিভাইস শিফটের সময়ে প্রায়ই প্লাগ ইন করা থাকে না।\n\nঅতিরিক্ত কনস্ট্রেইনট সাধারণত “সিঙ্ক কখনই চলেনি” রিপোর্টের কারণ। যদি আপনি unmetered Wi‑Fi, charging, এবং battery not low সবই চান, তাহলে প্রায় কখনোই উপযুক্ত মুহূর্ত পাবেন না। ব্যবসায় যদি আজ ডেটা দরকার হয়, তাহলে ছোট কাজগুলি বেশি বার চালানো ভালো—নির্দিষ্ট শর্তের জন্য অপেক্ষা করার চেয়ে।\n\nক্যাপটিভ পোর্টাল আরেক বাস্তব সমস্যা: ফোন বলছে এটি সংযুক্ত, কিন্তু ব্যবহারকারীকে হোটেল বা পাবলিক Wi‑Fi পৃষ্ঠায় “Accept” টিপতে হবে। WorkManager এটা নির্ভরযোগ্যভাবে সনাক্ত করতে পারে না। এটাকে সাধারণ ব্যর্থতা হিসেবে ধরুন: সিঙ্ক চেষ্টা করুন, দ্রুত টাইমআউট দিন, এবং পরে রিট্রাই করুন। অনুরোধ চলাকালে এটি সনাক্ত করতে পারলে একটি সরল ইন-অ্যাপ বার্তা দেখান যেমন “Wi‑Fi-এ সংযুক্ত কিন্তু ইন্টারনেট নেই”।\n\nছোট বনাম বড় আপলোডের জন্য আলাদা কনস্ট্রেইনট ব্যবহার করুন যাতে অ্যাপ responsive থাকে:\n\n- ছোট পে-লোড (স্ট্যাটাস পিং, ফর্ম মেটাডেটা): যে কোনও নেটওয়ার্ক, ব্যাটারি নট লো।\n- বড় পে-লোড (ফটো, ভিডিও, ম্যাপ প্যাক): সম্ভব হলে unmetered নেটওয়ার্ক এবং চার্জিং বিবেচনা করুন।\n\nউদাহরণ: একজন টেকনিশিয়ান 2টি ফটোসহ একটি ফর্ম সেভ করে। ফর্ম ফিল্ডরা যেকোনো কানেকশনে সাবমিট করুন, কিন্তু ফটো আপলোডগুলো Wi‑Fi বা ভালো মুহূর্তে কিউ করুন। অফিস দ্রুত কাজটা দেখে পায়, এবং ডিভাইস মোবাইল ডেটা ব্যাকগ্রাউন্ডে ছবি আপলোড করে খায় না।\n\n## ব্যবহারকারী বিরক্ত না করে এক্সপোনেনশিয়াল ব্যাকঅফ সহ রিট্রাই\n\nরিট্রাই হলো যেখানে ফিল্ড অ্যাপগুলো শান্ত লাগে না খারাপ লাগে—সঠিক ব্যাকঅফ পলিসি চয়ন করুন যা আপনার প্রত্যাশিত ব্যর্থতার ধরন মেলে।\n\nনেটওয়ার্ক-এর জন্য এক্সপোনেনশিয়াল ব্যাকঅফ সাধারণত নিরাপদ ডিফল্ট। এটি দ্রুত ওয়েট টাইম বাড়ায় যাতে সার্ভার উপর চাপ না পড়ে বা দুর্বল কভারেজে ব্যাটারি খরচ না হয়। লিনিয়ার ব্যাকঅফ ছোট সাময়িক সমস্যার জন্য ফিট করতে পারে, কিন্তু দুর্বল সিগনাল এলাকায় খুব ঘনঘন রিট্রাই করে।\n\nব্যর্থতার ধরন দেখে রিট্রাই সিদ্ধান্ত নিন, কেবল “কিছু ব্যর্থ হয়েছে” বলে না। একটি সহজ রুলসেট:\n\n- নেটওয়ার্ক টাইমআউট, 5xx, DNS, কোন কানেকটিভিটি না থাকা: Result.retry()\n- অথেন্টিকেশন মেয়াদোত্তীর্ণ (401): একবার টোকেন রিফ্রেশ করুন, তারপর ব্যর্থ হলে ইউজারকে সাইন-ইন বলুন\n- ভ্যালিডেশন বা 4xx (ব্যাড রিকুয়েস্ট): Result.failure() এবং সাপোর্টের জন্য স্পষ্ট ত্রুটি দেখান\n- কনফ্লিক্ট (409) যেগুলো ইতিমধ্যে পাঠানো আইটেম বোঝায়: যদি আপনার সিঙ্ক idempotent হয় তাহলে সেগুলোকে সফল হিসেবে বিবেচনা করুন\n\nস্থায়ী ত্রুটি যেন অনন্ত লুপ না করে তার জন্য ক্ষতি কাটিয়ে উঠুন। সর্বোচ্চ চেষ্টা সংখ্যা নির্ধারণ করুন, এবং তার পরে থামুন ও একটি নীরব, কাজযোগ্য বার্তা(surface) দেখান (পুনরায় নোটিফাই না করে)।\n\nচেষ্টা বাড়লে আচরণও বদলাতে পারেন। উদাহরণস্বরূপ, 2টি ব্যর্থতার পরে ছোট ব্যাচ পাঠান বা বড় আপলোড বাদ দিন যতক্ষণ না পরবর্তী সফল পুল হয়।\n\nkotlin\nval request = OneTimeWorkRequestBuilder\u003cSyncWorker\u003e()\n .setBackoffCriteria(\n BackoffPolicy.EXPONENTIAL,\n 30, TimeUnit.SECONDS\n )\n .build()\n\n// in doWork()\nif (runAttemptCount \u003e= 5) return Result.failure()\nreturn Result.retry()\n\n\nএটি রিট্রাইকে ভদ্র রাখে: কম ওয়েক-আপ, কম ব্যবহারকারী বিরক্তি, এবং কানেকশন ফিরে গেলে দ্রুত পুনরুদ্ধার।\n\n## ব্যবহারকারী-দৃশ্যমান অগ্রগতি: নোটিফিকেশন, ফোরগ্রাউন্ড কাজ, ও স্ট্যাটাস\n\nফিল্ড অ্যাপগুলো প্রায়ই সিঙ্ক তখন চালায় যখন ব্যবহারকারী প্রত্যাশা করে না: বেইসমেন্টে, ধীর নেটে, বা প্রায় ডেড ব্যাটারিতে। যদি সিঙ্ক ব্যবহারকারীর জন্য গুরুত্বপূর্ণ হয় (আপলোড, রিপোর্ট পাঠানো, ফটো ব্যাচ), তাহলে এটা দৃশ্যমান ও বোঝাপড়াযোগ্য করুন। ছোট তাৎক্ষণিক আপডেটগুলোর জন্য সাইলেন্ট ব্যাকগ্রাউন্ড কাজ দারুণ। দীর্ঘ যেকোনো কিছু হলে সেটা ইমানদার হওয়া উচিত।\n\n### কখন ফোরগ্রাউন্ড কাজ প্রয়োজন\n\nকাজ যদি দীর্ঘমেয়াদি, সময়-সংবেদনশীল, বা স্পষ্টভাবে একটি ব্যবহারকারী অ্যাকশনের সঙ্গে জড়িত হয় তাহলে ফোরগ্রাউন্ড এক্সিকিউশন ব্যবহার করুন। আধুনিক Android- এ বড় আপলোড স্টপ বা বিলম্বিত হতে পারে যদি আপনি ফোরগ্রাউন্ড না চালান। WorkManager-এ এর মানে হলো ForegroundInfo রিটার্ন করা যাতে সিস্টেম একটি চলমান নোটিফিকেশন দেখায়।\n\nএকটি ভালো নোটিফিকেশন তিনটি প্রশ্নের উত্তর দেয়: কী সিঙ্ক হচ্ছে, এটা কতটা এগিয়েছে, এবং কিভাবে বন্ধ করা যায়। একটি স্পষ্ট ক্যানসেল অ্যাকশন দিন যাতে ব্যবহারকারী মেটার্ড ডেটায় থাকলে বা ফোন এখনই লাগলে কাজ বন্ধ করতে পারে।\n\n### ব্যবহারকারীরা বিশ্বাসযোগ্য মনে রাখার মতো প্রগ্রেস\n\nপ্রগ্রেস বাস্তব ইউনিটের সঙ্গে মানানসই হওয়া উচিত, ঝাপসা শতাংশ নয়। setProgress দিয়ে আপডেট করুন এবং UI-তে (WorkInfo থেকে) পড়ুন।\n\nআপনি যদি 12টা ফটো এবং 3টা ফর্ম আপলোড করেন, বলুন “5 of 15 items uploaded”, কী বাকি আছে দেখান, এবং শেষ ত্রুটি মেসেজ সাপোর্টের জন্য রাখুন।\n\nপ্রগ্রেস মানে রাখুন:\n\n- সম্পন্ন আইটেম ও বাকি আইটেম\n- বর্তমান ধাপ ("Uploading photos", "Sending forms", "Finalizing")\n- শেষ সফল সিঙ্ক সময়\n- শেষ ত্রুটি (সংক্ষিপ্ত, ব্যবহারকারীর জন্য প্রাঞ্জল)\n- একটি দৃশ্যমান ক্যানসেল/স্টপ অপশন\n\nআপনি যদি আপনার টিম দ্রুত নিজস্ব টুল AppMaster দিয়ে বানান, একই নিয়ম রাখুন: ব্যবহারকারীরা সিঙ্কে বিশ্বাস তখনই করে যখন তারা এটা দেখতে পায় এবং সেটা তাদের বাস্তব কাজের সঙ্গে মেলে।\n\n## ইউনিক ওয়ার্ক, ট্যাগস, এবং ডুপ্লিকেট সিঙ্ক জব এড়ানো\n\nডুপ্লিকেট সিঙ্ক জব ব্যাটারি খরচ, মোবাইল ডেটা বাড়ানো এবং সার্ভার সাইড কনফ্লিক্ট তৈরি করার সহজ উপায়। WorkManager আপনাকে দুইটি সরল টুল দেয়: unique work name ও tags।\n\nএকটি ভালো ডিফল্ট হলো “sync” কে একটি সিঙ্গেল লেন ধরা। অ্যাপ যখন জাগে, নেটওয়ার্ক পরিবর্তন ঘটে এবং পিরিয়ডিক জব ট্রিগার করে একসাথে, তখন একাধিক জব enqueue না করে একই unique work name enqueue করুন—এভাবে sync storm হবে না।\n\nkotlin\nval request = OneTimeWorkRequestBuilder\u003cSyncWorker\u003e()\n .addTag(\"sync\")\n .build()\n\nWorkManager.getInstance(context)\n .enqueueUniqueWork(\"sync\", ExistingWorkPolicy.KEEP, request)\n\n\nপলিসি বাছাই হচ্ছে প্রধান আচরণগত সিদ্ধান্ত:\n\n- KEEP: যদি একটি সিঙ্ক ইতিমধ্যেই চলছে (বা কিউ করা আছে), নতুন অনুরোধ উপেক্ষা করা হবে। বেশিরভাগ “Sync now” বোতাম ও অটো-ট্রিগারের জন্য এটি ব্যবহার করুন।\n- REPLACE: বর্তমানটি বাতিল করে নতুন শুরু করবে। যখন ইনপুট সত্যিই বদলে যায় (যেমন ব্যবহারকারী অ্যাকাউন্ট বদলানো বা ভিন্ন প্রজেক্ট সিলেক্ট করা) তখন এটি ব্যবহার করুন।\n\nট্যাগগুলো কনট্রোল ও দৃশ্যমানতার জন্য হ্যান্ডেল। একটি স্থাযী ট্যাগ (sync) দিয়ে আপনি ক্যানসেল, স্ট্যাটাস চেক বা লগ ফিল্টার করতে পারবেন। এটি ম্যানুয়াল “sync now” অ্যাকশনের জন্য বিশেষভাবে উপকারী: আপনি চেক করতে পারেন কোন কাজ ইতোমধ্যে চলছে এবং নতুন worker লঞ্চ করার আগে ব্যবহারকারীকে একটি পরিষ্কার মেসেজ দেখাতে পারেন।\n\nপিরিয়ডিক ও অন-ডিমান্ড সিঙ্কই একে অপরের সাথে লড়াই করা উচিত নয়। আলাদা কিন্তু সমন্বিত রাখুন:\n\n- শিডিউলড কাজের জন্য enqueueUniquePeriodicWork("sync_periodic", KEEP, ...) ব্যবহার করুন।\n- অন-ডিমান্ডের জন্য enqueueUniqueWork("sync", KEEP, ...) ব্যবহার করুন।\n- worker-এ কিছু আপলোড বা ডাউনলোড না থাকলে দ্রুত exit করুন, যাতে পিরিয়ডিক রান সস্তা থাকে।\n- ঐচ্ছিকভাবে, পিরিয়ডিক worker একই one-time unique sync enqueue করতে পারে, যাতে বাস্তব কাজ একটি জায়গায় হয়।\n\nএই প্যাটার্নগুলো ব্যাকগ্রাউন্ড সিঙ্ককে প্রেডিক্টেবল রাখে: এক সময়ে এক সিঙ্ক, সহজে ক্যানসেলযোগ্য, এবং সহজে পর্যবেক্ষণযোগ্য।\n\n## ধাপে ধাপে: একটি প্র্যাকটিক্যাল ব্যাকগ্রাউন্ড সিঙ্ক পাইপলাইন\n\nএকটি নির্ভরযোগ্য সিঙ্ক পাইপলাইন ছোট একটি স্টেট মেশিন হিসেবে আচরণ করলে তৈরি করা সহজ: কাজগুলো প্রথমে লোকালি থাকে, আর WorkManager কেবল তখনই এগুলোকে সামনে বাড়ায় যখন শর্ত ঠিক থাকে।\n\n### একটি সরল পাইপলাইন যা আপনি পাঠাতে পারবেন\n\n1) লোকাল “queue” টেবিল দিয়ে শুরু করুন। পুনরায় আরম্ভ করতে সবচেয়ে কম মেটাডেটা সংরক্ষণ করুন: আইটেম আইডি, টাইপ (form, photo, note), স্ট্যাটাস (pending, uploading, done), attempts count, last error, এবং ডাউনলোডের জন্য কার্সর বা সার্ভার রিভিশন।\n\n2) ব্যবহারকারী ট্যাপ করলে “Sync now”–এর জন্য একটি OneTimeWorkRequest enqueue করুন যার কনস্ট্রেইনট আপনার বাস্তব বিশ্বের সাথে মেলে। সাধারণভাবে network connected এবং battery not low নির্বাচন করা হয়। যদি আপলোড ভারী হয়, তখন chargingও আবশ্যক করুন।\n\n3) একটি CoroutineWorker ইমপ্লিমেন্ট করুন ক্লিয়ার ফেজসহ: upload, download, reconcile। প্রতিটি ফেজ ইনক্রিমেন্টাল রাখুন। শুধুমাত্র pending মার্ক করা আইটেম আপলোড করুন, শুধুমাত্র আপনার শেষ কার্সরের পর থেকে পরিবর্তন ডাউনলোড করুন, তারপর কনফ্লিক্টকে সরল নিয়মে রিকনসাইল করুন (উদাহরণ: এসাইনমেন্ট ফিল্ডে সার্ভার জয়ী, লোকাল ড্রাফট নোটে ক্লায়েন্ট জয়ী)।\n\n4) ব্যাকঅফসহ রিট্রাই যোগ করুন, কিন্তু কি রিট্রাই করবেন সেটা বেছে নিন। টাইমআউট ও 500s রিট্রাই করবেন; 401 হলে দ্রুত ব্যর্থ করুন এবং UI-কে জানান।\n\n5) UI ও নোটিফিকেশনের জন্য WorkInfo পর্যবেক্ষণ করুন। “Uploading 3 of 10”–এর মতো ফেজগুলোর জন্য প্রগ্রেস আপডেট ব্যবহার করুন, এবং সংক্ষিপ্ত ত্রুটি বার্তা দেখান যা পরবর্তী করণীয় নির্দেশ করে (retry, sign in, connect to Wi‑Fi)।\n\nkotlin\nval constraints = Constraints.Builder()\n .setRequiredNetworkType(NetworkType.CONNECTED)\n .setRequiresBatteryNotLow(true)\n .build()\n\nval request = OneTimeWorkRequestBuilder\u003cSyncWorker\u003e()\n .setConstraints(constraints)\n .setBackoffCriteria(BackoffPolicy.EXPONENTIAL, 30, TimeUnit.SECONDS)\n .build()\n\n\nআপনি কিউ লোকাল রাখলে এবং worker ফেজগুলো explicit রাখলে, আচরণ প্রেডিক্টেবল হয়: কাজ স্থগিত, পুনরারম্ভ, এবং ব্যবহারকারীকে ব্যাখ্যা করতে পারে কোন কিছু ঘটল না ধরে নিয়ে না।\n\n## সাধারণ ভুল ও ফাঁদ (এবং সেগুলো কিভাবে এড়াবেন)\n\nনির্ভরযোগ্য সিঙ্ক প্রায়ই কয়েকটি ছোট সিদ্ধান্তের কারণে ব্যর্থ হয় যা ডেভ-ফোনে ঠিক মনে হলেও বাস্তবে ভেঙে পড়ে। লক্ষ্য হল যতটা সম্ভব ঠিক সময়ে কাজ চালানো—নিয়মিত চলার চেয়ে।\n\n### মনোযোগের ফাঁদগুলো\n\n- কনস্ট্রেইনট ছাড়া বড় আপলোড করা। যদি আপনি কোনও নেটওয়ার্ক ও কোনও ব্যাটারি লেভেলে ফটো পাঠান, ব্যবহারকারীরা তা অনুভব করবে। নেটওয়ার্ক টাইপ ও ব্যাটারি লো কনস্ট্রেইনট দিন, আর বড় কাজ ছোট ছোট টুকরোতে ভাগ করুন।\n- প্রতিটি ব্যর্থতাকে অনন্তকাল রিট্রাই করা। 401, মেয়াদোত্তীর্ণ টোকেন বা অনুপস্থিত অনুমতি অস্থায়ী সমস্যা নয়। এগুলোকে হার্ড ফেইল হিসেবে চিহ্নিত করুন, স্পষ্ট ক্রিয়া দেখান (পুনরায় লগইন), আর কেবল টাইমআউটের মতো ট্রানজিয়েন্ট সমস্যা রিট্রাই করুন।\n- দুর্ঘটনাক্রমে ডুপ্লিকেট তৈরি করা। যদি worker দুইবার চলতে পারে, সার্ভার ডবল ক্রিয়েট পাবে যদি অনুরোধ idempotent না হয়। প্রতিটি আইটেমে ক্লায়েন্ট-জেনারেট করা স্থায়ী ID ব্যবহার করুন এবং সার্ভারকে রিপিটকে আপডেট হিসেবে বিবেচনা করতে বলুন।\n- near real-time জন্য periodic work ব্যবহার করা। Periodic work মেইনটেন্যান্সের জন্য ভালো, কিন্তু “sync now”–এর জন্য নয়। ব্যবহারকারী-উদ্দিষ্ট সিঙ্কের জন্য one-time unique work enqueue করুন।\n- “100%” খুব তাড়াতাড়ি রিপোর্ট করা। আপলোড কমপ্লিশন মানেই ডেটা গ্রহণ ও রিকনসাইল হওয়া নয়। স্টেজ ধরে প্রগ্রেস ট্র্যাক করুন (queued, uploading, server confirmed) এবং কেবল সার্ভার কনফার্মেশনের পরে done দেখান।\n\nএকটি কংক্রিট উদাহরণ: একজন টেকনিশিয়ান তিনটি ছবিসহ একটি ফর্ম elevator-এ দুর্বল সিগনাল দিয়ে সাবমিট করে। যদি আপনি কোন কনস্ট্রেইনট না দিয়ে ফটো আপলোড শুরু করেন, আপলোড আটকে যাবে, রিট্রাই বাড়বে, এবং অ্যাপ রিস্টার্টে ফর্ম দুবার তৈরি হতে পারে। যদি আপনি ব্যবহারযোগ্য নেটওয়ার্ক কনস্ট্রেইনট দেন, ধাপে আপলোড করুন, এবং প্রতিটি ফর্মে স্থায়ী ID রাখেন, তাহলে একই সিনারিওতে সার্ভারে একটি পরিষ্কার রেকর্ড থাকবে ও প্রগ্রেস সত্য।\n\n## রিলিজের আগে দ্রুত চেকলিস্ট\n\nরিলিজের আগে বাস্তব ফিল্ড ব্যবহারকারীরা কিভাবে ভাঙবে সেটা মতো সিঙ্ক পরীক্ষা করুন: অনিয়মিত সিগনাল, ডেড ব্যাটারি, এবং অনেক ট্যাপ। ডেভ ফোনে ঠিক দেখলেই বাস্তব ডিভাইসে scheduling, retries বা স্ট্যাটাস রিপোর্টিং সমস্যা হলে তা ফেল হয়ে যাবে।\n\nকমপক্ষে একটি পুরনো ডিভাইস ও একটি নতুন ডিভাইসে এগুলো চালান। লোগ রাখুন, কিন্তু UI-তে ব্যবহারকারী কী দেখছে তাও দেখুন।\n\n- নোনেটওয়ার্ক, তারপর রিকভারি: কানেকটিভিটি বন্ধ রেখে সিঙ্ক শুরু করুন, পরে চালু করুন। নিশ্চিত করুন কাজ কিউ করা আছে (ততক্ষণাৎ ব্যর্থ নয়) এবং পুনরায় চালু হলে ডুপ্লিকেট ছাড়া পুনরায় শুরু হয়।\n- ডিভাইস রিস্টার্ট: সিঙ্ক শুরু করুন, মাঝপথে রিবুট দিন, তারপর অ্যাপ খুলুন। যাচাই করুন কাজ চালিয়ে যায় বা রি-শেডিউল হয় এবং UI সঠিক স্টেট দেখায় ("syncing" এ আটক নয়)।\n- লো ব্যাটারি ও কম স্টোরেজ: ব্যাটারি সেভার অন করুন, সম্ভব হলে লো ব্যাটারি থ্রেশহোল্ডে নামান, এবং স্টোরেজ প্রায় পূর্ণ করুন। নিশ্চিত করুন কাজ অপেক্ষা করে এবং শর্ত উন্নত হলে চালিয়ে যায়, পুনরায় রিট্রাই লুপে ব্যাটারি খরচ করে না।\n- অনেকবার ট্রিগার: Sync বোতাম একাধিকবার ট্যাপ করুন বা বিভিন্ন স্ক্রিন থেকে ট্রিগার করুন। আপনাকে একটি লজিক্যাল সিঙ্ক রান পাওয়া উচিত, প্যারালাল worker-এর পরিবর্তে।\n- সার্ভার ব্যর্থতা যেগুলো ব্যাখ্যাযোগ্য: 500s, টাইমআউট, ও auth ত্রুটি সিমুলেট করুন। যাচাই করুন রিট্রাই ব্যাকঅফ করে এবং একটি ক্যাপের পরে থামে, এবং ব্যবহারকারী একটি স্পষ্ট মেসেজ দেখে ("Can’t reach server, will retry")—জেনেরিক ত্রুটি নয়।\n\nযদি কোনো টেস্ট অ্যাপকে অস্পষ্ট অবস্থায় ফেলে, সেটাকে বাগ হিসেবে বিবেচনা করুন। ব্যবহারকারীরা ধীর সিঙ্ক ক্ষমা করে দেন, কিন্তু ডেটা হারানো বা বুঝতে না পারা মাফ করে না।\n\n## উদাহরণ পরিস্থিতি: অফলাইন ফর্ম ও ফটো আপলোড ফিল্ড অ্যাপে\n\nএকজন টেকনিশিয়ান দুর্বল কভারেজে সাইটে পৌঁছে। তারা অফলাইনে একটি সার্ভিস ফর্ম পূরণ করে, 12টি ছবি ক্যাপচার করে, এবং Submit ট্যাপ করে। অ্যাপ প্রথমে সবকিছু লোকালি সংরক্ষণ করে (উদাহরণ: লোকাল ডাটাবেসে): একটি রেকর্ড ফর্মের জন্য, এবং প্রতিটি ছবির জন্য একটি রেকর্ড স্পষ্ট স্টেট সহ যেমন PENDING, UPLOADING, DONE, বা FAILED।\n\nতারা Submit ট্যাপ করলে অ্যাপ একটি unique sync job enqueue করে যাতে তারা দুইবার ট্যাপ করলে ডুপ্লিকেট না হয়। একটি সাধারণ সেটআপ হলো WorkManager চেইন: প্রথমে ফটো আপলোড (বড়, ধীর), তারপর attachments কনফার্ম হলে ফর্ম পে-লোড পাঠানো।\n\nসিঙ্ক তখনই চলে যখন শর্ত বাস্তব জীবনের সঙ্গে মেলে—উদাহরণ: সংযুক্ত নেটওয়ার্ক, নন-লো ব্যাটারি, এবং পর্যাপ্ত স্টোরেজ। যদি টেক এখনো বেইসমেন্টে থাকে, কিছুই ব্যাকগ্রাউন্ডে লুপ করে ব্যাটারি খাবে না।\n\nপ্রগেস স্পষ্ট ও ব্যবহারকারী-ফ্রেন্ডলি। আপলোড ফোরগ্রাউন্ড কাজে চলে এবং একটি নোটিফিকেশন দেখায় “Uploading 3 of 12”, একটি স্পষ্ট Cancel অপশনের সঙ্গে। যদি তারা ক্যানসেল করে, অ্যাপ কাজ বন্ধ করে এবং বাকি আইটেমগুলো PENDING রেখেই রাখে যাতে পরে রিট্রাই করা যায় ডেটা না হারিয়ে।\n\nফ্ল্যাকি হটস্পটের পরে রিট্রাই ভদ্রভাবে আচরণ করে: প্রথম ব্যর্থতা দ্রুত রিট্রাই করে, কিন্তু প্রতিবার ব্যর্থ হলে অপেক্ষা বাড়ে (এক্সপোনেনশিয়াল ব্যাকঅফ)। শুরুতে এটি প্রতিক্রিয়াশীল মনে হয়, পরে ব্যাটারি ও নেটওয়ার্ক স্প্যাম এড়াতে ব্যাক-অফ করে।\n\nঅপস টিমের জন্য ফলাফল কার্যকর: কম ডুপ্লিকেট জমা কারণ আইটেমগুলো idempotent ও ইউনিক কিউ করা আছে, স্পষ্ট ব্যর্থতা স্টেট (কোন ছবি ব্যর্থ হয়েছে, কেন, কখন সেটা রিট্রাই করবে), এবং “submitted” মানে “নিরাপদে সংরক্ষিত এবং সিঙ্ক হবে” বলে বড় বিশ্বাস।\n\n## পরবর্তী ধাপ: প্রথমে নির্ভরযোগ্যতা পাঠান, তারপর সিঙ্ক স্কোপ বাড়ান\n\nআরও সিঙ্ক ফিচার যোগ করার আগে, স্পষ্টভাবে নির্ধারণ করুন “ক완” কী মানে। বেশিরভাগ ফিল্ড অ্যাপের জন্য এটা হলো কেবল “রিকুয়েস্ট পাঠানো” নয়—এটা হলো “সার্ভার গ্রহণ করেছে ও কনফার্ম করেছে”, এবং UI-র স্টেট বাস্তবতার সঙ্গে মেলে। একটি ফর্ম যা “Synced” বলে সেটি অ্যাপ রিস্টার্টের পরে সেই অবস্থায় থাকা উচিত, এবং একটি ব্যর্থ ফর্ম পরবর্তী করণীয় দেখানো উচিত।\n\nব্যবহারকারীদের বিশ্বাস করতে সহজ করুন কিছু স্পষ্ট সিগন্যাল যোগ করে যা সাপোর্ট জিজ্ঞেস করলে জানা যায়। সেগুলো সহজ ও consistent রাখুন across screen:\n\n- শেষ সফল সিঙ্ক সময়\n- শেষ সিঙ্ক ত্রুটি (সংক্ষিপ্ত মেসেজ, স্ট্যাক ট্রেস নয়)\n- পেন্ডিং আইটেম (উদাহরণ: 3 ফর্ম, 12 ফটো)\n- বর্তমান সিঙ্ক স্টেট (Idle, Syncing, Needs attention)\n\nপর্যবেক্ষণযোগ্যতাকে ফিচারের অংশ হিসেবে বিবেচনা করুন। এটা দুর্বল কানেকশনে পনেরো মিনিট বাঁচায় যখন কেউ জানে না অ্যাপ কাজ করছে কি না।\n\nআপনি যদি ব্যাকএন্ড ও অ্যাডমিন টুলও তৈরি করেন, সেগুলো একসাথে জেনারেট করলে সিঙ্ক কনট্রাক্ট স্থিতিশীল রাখা সহজ হয়। AppMaster (appmaster.io) আপনাকে প্রোডাকশন-রেডি ব্যাকএন্ড, একটি ওয়েব অ্যাডমিন প্যানেল, এবং নেটিভ মোবাইল অ্যাপ জেনারেট করতে পারে—এটি মডেল ও auth সঙ্গত রাখতে সাহায্য করে যাতে আপনি জটিল সিঙ্ক এজগুলোর উপর ফোকাস করতে পারেন।\n\nশেষে, একটি ছোট পাইলট চালান। একটি end-to-end সিঙ্ক স্লাইস বেছে নিন (উদাহরণ: “1–2 ফটোর সঙ্গে এক ইন্সপেকশন ফর্ম সাবমিট”), এবং সেটি constraints, retries, এবং ব্যবহারকারী-দৃশ্যমান প্রগ্রেস সহ পুরোপুরি কাজ করবে এমনভাবে পাঠান। যখন সেই স্লাইস বিরক্তিকর ও predictable হয়ে যায়, তখন একবারে একটি ফিচার করে বাড়ান।
প্রশ্নোত্তর
নির্ভরযোগ্য ব্যাকগ্রাউন্ড সিঙ্ক অর্থ হলো ডিভাইসে তৈরি কাজগুলো আগে লোকালি সংরক্ষিত হয় এবং পরে ব্যবহারকারীকে একই কাজ বারবার করতে না দিয়ে আপলোড হয়। অ্যাপ বন্ধ হয়ে যাওয়া, রিবুট, দুর্বল নেটওয়ার্ক বা রিট্রাইয়ের সময়ও ডেটা লুপ্ত না হওয়া এবং ডুপ্লিকেট না হওয়াই লক্ষ্য।
ইভেন্ট-ভিত্তিক বা ব্যবহারকারী-ট্রিগার করা কাজগুলোর জন্য OneTimeWorkRequest ব্যবহার করুন (যেমন ফর্ম সংরক্ষণ, ছবি যোগ করা, ব্যবহারকারী Sync টেপ করা)। PeriodicWorkRequest ব্যবহার করুন রক্ষণাবেক্ষণ বা ন্যাইটলি ক্লিনআপের মতো কাজের জন্য, কিন্তু গুরুত্বপূর্ণ আপলোডগুলোর একমাত্র পথ হিসেবে না—কারণ এর সময়সীমা ভিন্নতা থাকতে পারে।
Kotlin ব্যবহার করলে এবং আপনার সিঙ্ক কোড suspend ফাংশন ব্যবহার করলে CoroutineWorker সবচেয়ে সহজ ও প্রত্যাশিত আচরণ দেয় (বিশেষত ক্যানসেলেশনের ক্ষেত্রে)। ছোট ব্লকিং কাজের জন্য Worker উপযুক্ত, আর RxWorker ব্যবহার করবেন কেবল তখনই যখন অ্যাপ ইতোমধ্যেই RxJava-এর ওপর ভিত্তি করে গঠিত।
স্টেপগুলোর আলাদা কনস্ট্রেইনট থাকে বা আলাদা রিটারির দরকার হলে workers চেইন করুন (যেমন Wi‑Fi-এ বড় আপলোড, তারপর যে কোনও নেটে ছোট API কল)। স্টেপগুলো শেয়ারড ডেটা করে এবং একটিই ট্রানজেকশনের মতো আচরণ করলে একটি worker-এ ক্লিয়ার ফেজ রাখুন—এটি “all-or-nothing” শব্দটি দেয়।
প্রতিটি create/update অনুরোধ দুইবার চালানোই নিরাপদ হওয়া উচিত—উদাহরণস্বরূপ প্রতিটি আইটেমে একটি idempotency key (সাধারণত একটি UUID) সংরক্ষণ করুন যাতে সার্ভার একই অনুরোধকে একই ফলাফল হিসেবে বিবেচনা করে। সার্ভার বদলাতে না পারলে stable natural key এবং upsert এন্ডপয়েন্ট বা ভার্সন নম্বর ব্যবহার করুন যাতে stale আপডেট প্রত্যাখ্যাত হয়।
স্পষ্ট লোকাল স্টেট সংরক্ষণ করুন (যেমন queued, uploading, uploaded, failed) যাতে worker ক্র্যাশের পরে অনুমান না করে পুনরায় চালিয়ে দিতে পারে। প্রতিটি আইটেম সার্ভার কনফার্ম না দিলে done মার্ক করবেন না—ফাইল URI ও আপলোড মেটাডেটাও সংরক্ষণ করুন যাতে পুনরায় আরম্ভ করলে শেষ অবস্থান থেকে চালানো যায়।
একটি বাস্তবভিত্তিক বেসলাইন হলো: নেটওয়ার্ক থাকা উচিত, ব্যাটারি লো হলে চালাবেন না, এবং স্টোরেজ ক্রিটিক্যাল না থাকলে চালান। কাজ ভারী হলে চার্জিং প্রয়োজন বলুন; কিন্তু খুব বেশি কনস্ট্রেইনট দিলে সিঙ্ক প্রায় কখনোই চলবে না—অতএব “unmetered” বা “charging” প্রয়োজন হলে সতর্ক হোন।
ক্যাপটিভ পোর্টাল বা “Wi‑Fi আছে কিন্তু ইন্টারনেট নেই” এমন পরিস্থিতিকে সাধারণ ব্যর্থতা হিসেবে বিবেচনা করুন: দ্রুত টাইমআউট করুন, Result.retry() দিন এবং পরে আবার চেষ্টা করুন। অনুরোধ চলাকালে যদি সনাক্ত করতে পারেন, একটি সরল মেসেজ দেখান যাতে ব্যবহারকারী বুঝতে পারে কেন ডিভাইস অনলাইন মনে হলেও সিঙ্ক চলছে না।
দুর্বল নেটওয়ার্কের জন্য সাধারণত এক্সপোনেনশিয়াল ব্যাকঅফ নিরাপদ ডিফল্ট। টাইমআউট ও 5xx-এর জন্য রিট্রাই করুন, 401 হলে টোকেন রিফ্রেশ একবার করে তারপর ফেইল করুন এবং ব্যবহারকারীকে সাইন-ইন ভাবতে বলুন, 4xx (ভুল অনুরোধ) হলে স্পষ্ট ত্রুটি দেখান এবং পুনরায় চেষ্টা করবেন না। সর্বোমোট চেষ্টা সংখ্যা সীমাবদ্ধ রাখুন যাতে স্থায়ী ত্রুটি অশেষ লুপ না তৈরি করে।
বহু ট্রিগার একসাথে parallel জব শুরু না করে sync-কে unique work হিসেবে enqueue করুন যাতে sync storms না হয়। দীর্ঘ কাজ হলে foreground execution ব্যবহার করুন এবং একটি চলমান নোটিফিকেশন দেখান যা বাস্তব কপCounটসমূহ দেখায় (যেমন “5 of 15 items uploaded”) এবং স্পষ্ট একটি Cancel অপশন রাখুন।


