একটি অফলাইন-প্রথম অ্যাপ তৈরি করুন

একটি অফলাইন-প্রথম অ্যাপ হল এমন একটি অ্যাপ যা ইন্টারনেটে অ্যাক্সেস ছাড়াই সমস্ত কিছু বা এর মূল কার্যকারিতার একটি গুরুত্বপূর্ণ উপসেট সম্পাদন করতে সক্ষম। অর্থাৎ, এটি অফলাইনে তার ব্যবসার কিছু বা সমস্ত যুক্তি সম্পাদন করতে পারে।

একটি অফলাইন-প্রথম অ্যাপ তৈরির জন্য বিবেচনাগুলি ডেটা স্তরে শুরু হয় যা অ্যাপ্লিকেশন ডেটা এবং ব্যবসায়িক যুক্তিতে অ্যাক্সেস সরবরাহ করে। ডিভাইসের বাহ্যিক উৎস থেকে অ্যাপটিকে সময়ে সময়ে এই ডেটা রিফ্রেশ করতে হতে পারে। এটি করার সময়, আপ টু ডেট থাকার জন্য নেটওয়ার্ক সংস্থানগুলিতে কল করার প্রয়োজন হতে পারে।

নেটওয়ার্ক প্রাপ্যতা সবসময় নিশ্চিত করা হয় না. ডিভাইসগুলিতে সাধারণত স্পিটি বা ধীর নেটওয়ার্ক সংযোগের সময়কাল থাকে। ব্যবহারকারীদের নিম্নলিখিত অভিজ্ঞতা হতে পারে:

  • সীমিত ইন্টারনেট ব্যান্ডউইথ
  • ক্ষণস্থায়ী সংযোগ বাধা, যেমন যখন একটি লিফট বা একটি টানেলে।
  • মাঝে মাঝে ডেটা অ্যাক্সেস। উদাহরণস্বরূপ, শুধুমাত্র ওয়াইফাই ট্যাবলেট।

কারণ যাই হোক না কেন, এই পরিস্থিতিতে একটি অ্যাপের পক্ষে পর্যাপ্তভাবে কাজ করা প্রায়শই সম্ভব। আপনার অ্যাপ অফলাইনে সঠিকভাবে কাজ করে তা নিশ্চিত করতে, এটি নিম্নলিখিতগুলি করতে সক্ষম হওয়া উচিত:

  • একটি নির্ভরযোগ্য নেটওয়ার্ক সংযোগ ছাড়া ব্যবহারযোগ্য থাকুন।
  • প্রথম নেটওয়ার্ক কল সম্পূর্ণ বা ব্যর্থ হওয়ার জন্য অপেক্ষা না করে অবিলম্বে স্থানীয় ডেটা সহ ব্যবহারকারীদের উপস্থাপন করুন।
  • ব্যাটারি এবং ডেটা স্থিতি সম্পর্কে সচেতন এমনভাবে ডেটা আনুন৷ উদাহরণস্বরূপ, শুধুমাত্র সর্বোত্তম অবস্থার অধীনে ডেটা আনার অনুরোধ করে, যেমন চার্জ করার সময় বা ওয়াইফাইতে।

একটি অ্যাপ যা উপরের মানদণ্ড পূরণ করতে পারে তাকে প্রায়ই অফলাইন-প্রথম অ্যাপ বলা হয়।

একটি অফলাইন-প্রথম অ্যাপ ডিজাইন করুন

একটি অফলাইন-প্রথম অ্যাপ ডিজাইন করার সময় আপনার ডেটা স্তর এবং দুটি প্রধান ক্রিয়াকলাপ শুরু করা উচিত যা আপনি অ্যাপ ডেটাতে সম্পাদন করতে পারেন:

  • রিডস : ব্যবহারকারীর কাছে তথ্য প্রদর্শনের মতো অ্যাপের অন্যান্য অংশ দ্বারা ব্যবহারের জন্য ডেটা পুনরুদ্ধার করা।
  • লিখেছেন : পরবর্তীতে পুনরুদ্ধারের জন্য ব্যবহারকারীর ইনপুট ধরে রাখা।

ডেটা স্তরের সংগ্রহস্থলগুলি অ্যাপ ডেটা সরবরাহ করতে ডেটা উত্সগুলিকে একত্রিত করার জন্য দায়ী৷ একটি অফলাইন-প্রথম অ্যাপ্লিকেশানে, অন্তত একটি ডেটা উত্স থাকতে হবে যেটির সবচেয়ে গুরুত্বপূর্ণ কাজগুলি সম্পাদন করার জন্য নেটওয়ার্ক অ্যাক্সেসের প্রয়োজন নেই৷ এই গুরুত্বপূর্ণ কাজগুলির মধ্যে একটি হল ডেটা পড়া।

একটি অফলাইন-প্রথম অ্যাপে মডেল ডেটা

একটি অফলাইন-প্রথম অ্যাপে নেটওয়ার্ক সংস্থানগুলি ব্যবহার করে এমন প্রতিটি সংগ্রহস্থলের জন্য ন্যূনতম 2টি ডেটা উত্স রয়েছে:

  • স্থানীয় তথ্য উৎস
  • নেটওয়ার্ক ডেটা উৎস
একটি অফলাইন-প্রথম ডেটা স্তর স্থানীয় এবং নেটওয়ার্ক ডেটা উত্স উভয়ের সমন্বয়ে গঠিত
চিত্র 1 : একটি অফলাইন-প্রথম সংগ্রহস্থল

স্থানীয় তথ্য উৎস

স্থানীয় ডেটা উৎস হল অ্যাপটির জন্য সত্যের প্রামাণিক উৎস । এটি যেকোন ডেটার একচেটিয়া উৎস হওয়া উচিত যা অ্যাপের উচ্চ স্তরগুলি পড়ে৷ এটি সংযোগ অবস্থার মধ্যে ডেটা সামঞ্জস্য নিশ্চিত করে। স্থানীয় তথ্য উৎস প্রায়ই স্টোরেজ দ্বারা সমর্থিত হয় যা ডিস্কে স্থির থাকে। ডিস্কে ডেটা স্থায়ী করার কিছু সাধারণ উপায় হল:

  • স্ট্রাকচার্ড ডেটা সোর্স, যেমন রিলেশনাল ডাটাবেস যেমন রুম
  • অসংগঠিত তথ্য উত্স. উদাহরণস্বরূপ, ডেটাস্টোরের সাথে প্রোটোকল বাফার।
  • সহজ ফাইল

নেটওয়ার্ক ডেটা উৎস

নেটওয়ার্ক ডেটা উৎস হল অ্যাপ্লিকেশনটির প্রকৃত অবস্থা। স্থানীয় ডেটা উত্স নেটওয়ার্ক ডেটা উত্সের সাথে সর্বোত্তম সিঙ্ক্রোনাইজ করা হয়৷ এটি পিছিয়েও থাকতে পারে, এই ক্ষেত্রে অনলাইনে ফিরে আসার সময় অ্যাপটিকে আপডেট করতে হবে। বিপরীতভাবে, নেটওয়ার্ক ডেটা উত্স স্থানীয় ডেটা উত্স থেকে পিছিয়ে থাকতে পারে যতক্ষণ না অ্যাপটি সংযোগ ফিরে আসার পরে এটি আপডেট করতে পারে। অ্যাপের ডোমেন এবং UI স্তরগুলি কখনই সরাসরি নেটওয়ার্ক স্তরের সাথে যোগাযোগ করা উচিত নয়৷ হোস্টিং repository দায়িত্ব এর সাথে যোগাযোগ করা, এবং স্থানীয় ডেটা সোর্স আপডেট করার জন্য এটি ব্যবহার করা।

সম্পদ প্রকাশ করা

আপনার অ্যাপ কীভাবে তাদের পড়তে এবং লিখতে পারে তার স্থানীয় এবং নেটওয়ার্ক ডেটা উত্সগুলি মৌলিকভাবে আলাদা হতে পারে। একটি স্থানীয় ডেটা উৎসের অনুসন্ধান দ্রুত এবং নমনীয় হতে পারে, যেমন SQL কোয়েরি ব্যবহার করার সময়। বিপরীতভাবে, নেটওয়ার্ক ডেটা উত্সগুলি ধীর এবং সীমাবদ্ধ হতে পারে, যেমন আইডি দ্বারা ক্রমবর্ধমানভাবে RESTful সংস্থানগুলি অ্যাক্সেস করা। ফলস্বরূপ, প্রতিটি ডেটা উত্সের প্রায়শই এটি সরবরাহ করা ডেটার নিজস্ব উপস্থাপনা প্রয়োজন। স্থানীয় ডাটা সোর্স এবং নেটওয়ার্ক ডাটা সোর্স এর নিজস্ব মডেল থাকতে পারে।

নীচের ডিরেক্টরি কাঠামো এই ধারণাটি কল্পনা করে। AuthorEntity হল অ্যাপের স্থানীয় ডাটাবেস থেকে পড়া একজন লেখকের প্রতিনিধিত্ব, এবং NetworkAuthor হল নেটওয়ার্কে সিরিয়াল করা একজন লেখকের উপস্থাপনা:

data/
├─ local/
│ ├─ entities/
│ │ ├─ AuthorEntity
│ ├─ dao/
│ ├─ NiADatabase
├─ network/
│ ├─ NiANetwork
│ ├─ models/
│ │ ├─ NetworkAuthor
├─ model/
│ ├─ Author
├─ repository/

AuthorEntity এবং NetworkAuthor এর বিশদ বিবরণ অনুসরণ করে:

/**
 * Network representation of [Author]
 */
@Serializable
data class NetworkAuthor(
    val id: String,
    val name: String,
    val imageUrl: String,
    val twitter: String,
    val mediumPage: String,
    val bio: String,
)

/**
 * Defines an author for either an [EpisodeEntity] or [NewsResourceEntity].
 * It has a many-to-many relationship with both entities
 */
@Entity(tableName = "authors")
data class AuthorEntity(
    @PrimaryKey
    val id: String,
    val name: String,
    @ColumnInfo(name = "image_url")
    val imageUrl: String,
    @ColumnInfo(defaultValue = "")
    val twitter: String,
    @ColumnInfo(name = "medium_page", defaultValue = "")
    val mediumPage: String,
    @ColumnInfo(defaultValue = "")
    val bio: String,
)

AuthorEntity এবং NetworkAuthor উভয়কেই ডেটা স্তরের অভ্যন্তরীণ রাখা এবং বহিরাগত স্তরগুলি ব্যবহার করার জন্য তৃতীয় প্রকার প্রকাশ করা ভাল অনুশীলন। এটি বাহ্যিক স্তরগুলিকে স্থানীয় এবং নেটওয়ার্ক ডেটা উত্সগুলিতে ছোটখাটো পরিবর্তন থেকে রক্ষা করে যা অ্যাপের আচরণকে মৌলিকভাবে পরিবর্তন করে না। এটি নিম্নলিখিত স্নিপেটে প্রদর্শিত হয়:

/**
 * External data layer representation of a "Now in Android" Author
 */
data class Author(
    val id: String,
    val name: String,
    val imageUrl: String,
    val twitter: String,
    val mediumPage: String,
    val bio: String,
)

তারপরে নেটওয়ার্ক মডেলটি স্থানীয় মডেলে রূপান্তর করার জন্য একটি এক্সটেনশন পদ্ধতি সংজ্ঞায়িত করতে পারে এবং স্থানীয় মডেলের একইভাবে এটিকে বাহ্যিক উপস্থাপনায় রূপান্তর করার জন্য নীচে দেখানো হয়েছে:

/**
 * Converts the network model to the local model for persisting
 * by the local data source
 */
fun NetworkAuthor.asEntity() = AuthorEntity(
    id = id,
    name = name,
    imageUrl = imageUrl,
    twitter = twitter,
    mediumPage = mediumPage,
    bio = bio,
)

/**
 * Converts the local model to the external model for use
 * by layers external to the data layer
 */
fun AuthorEntity.asExternalModel() = Author(
    id = id,
    name = name,
    imageUrl = imageUrl,
    twitter = twitter,
    mediumPage = mediumPage,
    bio = bio,
)

পড়ে

রিড হল একটি অফলাইন-প্রথম অ্যাপে অ্যাপ ডেটার মৌলিক কাজ। তাই আপনাকে অবশ্যই নিশ্চিত করতে হবে যে আপনার অ্যাপটি ডেটা পড়তে পারে এবং নতুন ডেটা উপলব্ধ হওয়ার সাথে সাথে অ্যাপটি এটি প্রদর্শন করতে পারে। একটি অ্যাপ্লিকেশন যা এটি করতে পারে একটি প্রতিক্রিয়াশীল অ্যাপ্লিকেশন কারণ তারা পর্যবেক্ষণযোগ্য প্রকারের সাথে রিড এপিআই প্রকাশ করে।

নীচের স্নিপেটে, OfflineFirstTopicRepository তার সমস্ত পঠিত API-এর জন্য Flows প্রদান করে। এটি নেটওয়ার্ক ডেটা উৎস থেকে আপডেট গ্রহণ করার সময় এটির পাঠকদের আপডেট করার অনুমতি দেয়। অন্য কথায়, এটি OfflineFirstTopicRepository পুশ পরিবর্তনের অনুমতি দেয় যখন এটির স্থানীয় ডেটা উৎস অবৈধ হয়ে যায়। অতএব, OfflineFirstTopicRepository এর প্রতিটি পাঠককে অবশ্যই ডেটা পরিবর্তনগুলি পরিচালনা করার জন্য প্রস্তুত থাকতে হবে যা অ্যাপে নেটওয়ার্ক সংযোগ পুনরুদ্ধার করার সময় ট্রিগার হতে পারে। উপরন্তু, OfflineFirstTopicRepository স্থানীয় ডেটা উৎস থেকে সরাসরি ডেটা পড়ে। এটি শুধুমাত্র প্রথমে তার স্থানীয় ডেটা উৎস আপডেট করে ডেটা পরিবর্তনের বিষয়ে পাঠকদের অবহিত করতে পারে।

class OfflineFirstTopicsRepository(
    private val topicDao: TopicDao,
    private val network: NiaNetworkDataSource,
) : TopicsRepository {

    override fun getTopicsStream(): Flow<List<Topic>> =
        topicDao.getTopicEntitiesStream()
            .map { it.map(TopicEntity::asExternalModel) }
}

ত্রুটি পরিচালনার কৌশল

অফলাইন-প্রথম অ্যাপ্লিকেশানগুলিতে ত্রুটিগুলি পরিচালনা করার অনন্য উপায় রয়েছে, যেখানে সেগুলি ঘটতে পারে সেই ডেটা উত্সগুলির উপর নির্ভর করে৷ নিম্নলিখিত উপধারা এই কৌশল রূপরেখা.

স্থানীয় তথ্য উৎস

স্থানীয় ডেটা উত্স থেকে পড়ার সময় ত্রুটিগুলি বিরল হওয়া উচিত। পাঠকদের ত্রুটি থেকে রক্ষা করতে, Flows catch অপারেটর ব্যবহার করুন যেখান থেকে পাঠক ডেটা সংগ্রহ করছে।

একটি ViewModelcatch অপারেটরের ব্যবহার নিম্নরূপ:

class AuthorViewModel(
    authorsRepository: AuthorsRepository,
    ...
) : ViewModel() {
   private val authorId: String = ...

   // Observe author information
    private val authorStream: Flow<Author> =
        authorsRepository.getAuthorStream(
            id = authorId
        )
        .catch { emit(Author.empty()) }
}

নেটওয়ার্ক ডেটা উৎস

নেটওয়ার্ক ডেটা উৎস থেকে ডেটা পড়ার সময় ত্রুটি দেখা দিলে, অ্যাপটিকে ডেটা আনার পুনরায় চেষ্টা করার জন্য একটি হিউরিস্টিক নিয়োগ করতে হবে। সাধারণ হিউরিস্টিক অন্তর্ভুক্ত:

সূচকীয় ব্যাকঅফ

এক্সপোনেনশিয়াল ব্যাকঅফে , অ্যাপটি সফল না হওয়া পর্যন্ত নেটওয়ার্ক ডেটা সোর্স থেকে ক্রমবর্ধমান সময়ের ব্যবধানে পড়ার চেষ্টা চালিয়ে যায়, অথবা অন্যান্য শর্তগুলি নির্দেশ করে যে এটি বন্ধ করা উচিত।

সূচকীয় ব্যাকঅফ সহ ডেটা পড়া
চিত্র 2 : সূচকীয় ব্যাকঅফ সহ ডেটা পড়া

অ্যাপটি ব্যাক অফ রাখা উচিত কিনা তা মূল্যায়ন করার মানদণ্ডের মধ্যে রয়েছে:

  • নেটওয়ার্ক ডেটা উৎস নির্দেশিত ত্রুটির ধরনের. উদাহরণস্বরূপ, আপনার নেটওয়ার্ক কলগুলি পুনরায় চেষ্টা করা উচিত যা একটি ত্রুটি ফেরত দেয় যা সংযোগের অভাব নির্দেশ করে৷ বিপরীতভাবে, যথাযথ শংসাপত্র উপলব্ধ না হওয়া পর্যন্ত অনুমোদিত নয় এমন HTTP অনুরোধগুলির জন্য আপনার পুনরায় চেষ্টা করা উচিত নয়।
  • সর্বাধিক অনুমোদিত পুনরায় চেষ্টা.
নেটওয়ার্ক সংযোগ পর্যবেক্ষণ

এই পদ্ধতিতে, অ্যাপটি নিশ্চিত না হওয়া পর্যন্ত পড়ার অনুরোধগুলি সারিবদ্ধ থাকে যতক্ষণ না এটি নেটওয়ার্ক ডেটা উৎসের সাথে সংযোগ করতে পারে। একবার সংযোগ স্থাপন করা হয়ে গেলে, পড়ার অনুরোধটি সারিবদ্ধ করা হয়, ডেটা রিড করা হয় এবং স্থানীয় ডেটা উত্স আপডেট করা হয়। অ্যান্ড্রয়েডে এই সারিটি একটি রুম ডাটাবেসের সাথে রক্ষণাবেক্ষণ করা যেতে পারে এবং ওয়ার্ক ম্যানেজার ব্যবহার করে অবিরাম কাজ হিসাবে নিষ্কাশন করা যেতে পারে।

নেটওয়ার্ক মনিটর এবং সারি দিয়ে ডেটা পড়া
চিত্র 3 : নেটওয়ার্ক পর্যবেক্ষণ সহ সারি পড়ুন

লেখে

যদিও অফলাইন-প্রথম অ্যাপে ডেটা পড়ার প্রস্তাবিত উপায় হল পর্যবেক্ষণযোগ্য প্রকারগুলি ব্যবহার করা, লেখার APIগুলির সমতুল্য হল অ্যাসিঙ্ক্রোনাস API যেমন সাসপেন্ড ফাংশন৷ এটি UI থ্রেড ব্লক করা এড়ায়, এবং ত্রুটি পরিচালনায় সহায়তা করে কারণ অফলাইন-প্রথম অ্যাপগুলিতে লেখা নেটওয়ার্ক সীমানা অতিক্রম করার সময় ব্যর্থ হতে পারে।

interface UserDataRepository {
    /**
     * Updates the bookmarked status for a news resource
     */
    suspend fun updateNewsResourceBookmark(newsResourceId: String, bookmarked: Boolean)
}

উপরের স্নিপেটে, পছন্দের অ্যাসিঙ্ক্রোনাস API হল Coroutines যেহেতু উপরের পদ্ধতিটি স্থগিত করা হয়েছে।

কৌশল লিখুন

অফলাইন-প্রথম অ্যাপগুলিতে ডেটা লেখার সময়, তিনটি কৌশল বিবেচনা করতে হবে। আপনি কোনটি বেছে নেবেন তা নির্ভর করে লেখার ডেটার ধরন এবং অ্যাপের প্রয়োজনীয়তার উপর:

অনলাইন-শুধু লেখেন

নেটওয়ার্ক সীমানা জুড়ে ডেটা লেখার চেষ্টা করুন। সফল হলে, স্থানীয় ডেটা উৎস আপডেট করুন, অন্যথায় একটি ব্যতিক্রম নিক্ষেপ করুন এবং যথাযথভাবে প্রতিক্রিয়া জানাতে কলারকে ছেড়ে দিন।

অনলাইনে শুধু লেখে
চিত্র 4 : অনলাইনে শুধুমাত্র লেখা

এই কৌশলটি প্রায়শই লিখিত লেনদেনের জন্য ব্যবহৃত হয় যা অনলাইনে কাছাকাছি বাস্তব সময়ে ঘটতে হবে। উদাহরণস্বরূপ, একটি ব্যাংক স্থানান্তর। যেহেতু লেখাগুলি ব্যর্থ হতে পারে, তাই প্রায়শই ব্যবহারকারীর সাথে যোগাযোগ করা প্রয়োজন যে লেখাটি ব্যর্থ হয়েছে, অথবা ব্যবহারকারীকে প্রথম স্থানে ডেটা লেখার চেষ্টা করা থেকে বিরত রাখতে হবে। এই পরিস্থিতিতে আপনি নিয়োগ করতে পারেন এমন কিছু কৌশল অন্তর্ভুক্ত হতে পারে:

  • যদি একটি অ্যাপের ডেটা লেখার জন্য ইন্টারনেট অ্যাক্সেসের প্রয়োজন হয়, তবে এটি ব্যবহারকারীর কাছে এমন একটি UI উপস্থাপন না করতে পারে যা ব্যবহারকারীকে ডেটা লিখতে দেয়, বা অন্ততপক্ষে এটি নিষ্ক্রিয় করে।
  • আপনি একটি পপ-আপ বার্তা ব্যবহার করতে পারেন যা ব্যবহারকারী বাতিল করতে পারে না, অথবা একটি ক্ষণস্থায়ী প্রম্পট, ব্যবহারকারীকে অবহিত করতে যে তারা অফলাইন রয়েছে৷

সারিবদ্ধ লিখেছেন

যখন আপনার কাছে একটি বস্তু থাকে যা আপনি লিখতে চান, এটি একটি সারিতে ঢোকান। অ্যাপটি অনলাইনে ফিরে এলে এক্সপোনেনশিয়াল ব্যাক অফ দিয়ে সারির ড্রেন করতে এগিয়ে যান। অ্যান্ড্রয়েডে, একটি অফলাইন সারি নিষ্কাশন করা ক্রমাগত কাজ যা প্রায়শই WorkManager কে অর্পণ করা হয়।

পুনরায় চেষ্টা করে সারি লিখুন
চিত্র 5 : পুনরায় চেষ্টা করে সারি লিখুন

এই পদ্ধতিটি একটি ভাল পছন্দ যদি:

  • এটা অপরিহার্য নয় যে ডেটা কখনও নেটওয়ার্কে লেখা হবে।
  • লেনদেন সময় সংবেদনশীল নয়.
  • অপারেশন ব্যর্থ হলে ব্যবহারকারীকে জানানো আবশ্যক নয়।

এই পদ্ধতির জন্য কেস ব্যবহার করুন বিশ্লেষণ ইভেন্ট এবং লগিং অন্তর্ভুক্ত.

অলস লিখেছেন

প্রথমে স্থানীয় ডেটা সোর্সে লিখুন, তারপর যত তাড়াতাড়ি সুবিধা হবে নেটওয়ার্ককে অবহিত করার জন্য রাইটটি সারিবদ্ধ করুন। এটি অ তুচ্ছ কারণ অ্যাপটি অনলাইনে ফিরে আসার সময় নেটওয়ার্ক এবং স্থানীয় ডেটা উত্সগুলির মধ্যে দ্বন্দ্ব হতে পারে৷ দ্বন্দ্ব সমাধানের পরবর্তী অধ্যায় আরও বিশদ প্রদান করে।

নেটওয়ার্ক মনিটরিং সহ লেজি লেখেন
চিত্র 6 : অলস লিখেছেন

অ্যাপের জন্য ডেটা গুরুত্বপূর্ণ হলে এই পদ্ধতিটিই সঠিক পছন্দ। উদাহরণ স্বরূপ, একটি অফলাইন-প্রথম করণীয় তালিকা অ্যাপে, ব্যবহারকারীর অফলাইনে যোগ করা যেকোনো কাজ ডেটা ক্ষতির ঝুঁকি এড়াতে স্থানীয়ভাবে সংরক্ষণ করা অপরিহার্য।

সিঙ্ক্রোনাইজেশন এবং দ্বন্দ্ব সমাধান

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

  • টান-ভিত্তিক সিঙ্ক্রোনাইজেশন
  • পুশ-ভিত্তিক সিঙ্ক্রোনাইজেশন

টান-ভিত্তিক সিঙ্ক্রোনাইজেশন

পুল-ভিত্তিক সিঙ্ক্রোনাইজেশনে, অ্যাপ্লিকেশনটি চাহিদা অনুযায়ী সর্বশেষ অ্যাপ্লিকেশন ডেটা পড়ার জন্য নেটওয়ার্কের কাছে পৌঁছায়। এই পদ্ধতির জন্য একটি সাধারণ হিউরিস্টিক হল নেভিগেশন-ভিত্তিক, যেখানে অ্যাপটি ব্যবহারকারীর কাছে এটি উপস্থাপন করার ঠিক আগে ডেটা নিয়ে আসে।

এই পদ্ধতিটি সবচেয়ে ভাল কাজ করে যখন অ্যাপটি কোনো নেটওয়ার্ক সংযোগ ছাড়াই সংক্ষিপ্ত থেকে মধ্যবর্তী সময়ের আশা করে। এর কারণ হল ডেটা রিফ্রেশ সুবিধাবাদী, এবং দীর্ঘ সময় সংযোগ না থাকলে ব্যবহারকারীর অ্যাপের গন্তব্যে যাওয়ার চেষ্টা করার সুযোগ বাড়ে যা হয় বাসি বা খালি।

টান ভিত্তিক সিঙ্ক্রোনাইজেশন
চিত্র 7 : পুল-ভিত্তিক সিঙ্ক্রোনাইজেশন: ডিভাইস A শুধুমাত্র স্ক্রীন A এবং B এর জন্য সংস্থানগুলি অ্যাক্সেস করে, যখন ডিভাইস B শুধুমাত্র B, C এবং D স্ক্রীনগুলির জন্য সংস্থানগুলি অ্যাক্সেস করে

একটি অ্যাপ বিবেচনা করুন যেখানে একটি নির্দিষ্ট স্ক্রিনের জন্য একটি অন্তহীন স্ক্রোলিং তালিকায় আইটেমগুলি আনতে পৃষ্ঠা টোকেনগুলি ব্যবহার করা হয়। বাস্তবায়ন অলসভাবে নেটওয়ার্কের কাছে পৌঁছাতে পারে, স্থানীয় ডেটা উত্সে ডেটা বজায় রাখতে পারে এবং তারপরে ব্যবহারকারীর কাছে তথ্য উপস্থাপন করতে স্থানীয় ডেটা উত্স থেকে পড়তে পারে। যে ক্ষেত্রে কোনো নেটওয়ার্ক সংযোগ নেই, সংগ্রহস্থল শুধুমাত্র স্থানীয় ডেটা উৎস থেকে ডেটা অনুরোধ করতে পারে। এটি জেটপ্যাক পেজিং লাইব্রেরি দ্বারা তার RemoteMediator API সহ ব্যবহৃত প্যাটার্ন।

class FeedRepository(...) {

    fun feedPagingSource(): PagingSource<FeedItem> { ... }
}

class FeedViewModel(
    private val repository: FeedRepository
) : ViewModel() {
    private val pager = Pager(
        config = PagingConfig(
            pageSize = NETWORK_PAGE_SIZE,
            enablePlaceholders = false
        ),
        remoteMediator = FeedRemoteMediator(...),
        pagingSourceFactory = feedRepository::feedPagingSource
    )

    val feedPagingData = pager.flow
}

পুল-ভিত্তিক সিঙ্ক্রোনাইজেশনের সুবিধা এবং অসুবিধাগুলি নীচের সারণীতে সংক্ষিপ্ত করা হয়েছে:

সুবিধাদি অসুবিধা
বাস্তবায়ন করা তুলনামূলকভাবে সহজ। ভারী ডেটা ব্যবহারের প্রবণ। এটি কারণ একটি নেভিগেশন গন্তব্যে বারবার পরিদর্শন অপরিবর্তিত তথ্যের অপ্রয়োজনীয় রিফেচিং ট্রিগার করে। আপনি সঠিক ক্যাশিংয়ের মাধ্যমে এটি প্রশমিত করতে পারেন। এটি cachedIn অপারেটরের সাথে UI স্তরে বা HTTP ক্যাশে সহ নেটওয়ার্ক স্তরে করা যেতে পারে।
প্রয়োজন নেই এমন ডেটা কখনই আনা হবে না। আপেক্ষিক ডেটার সাথে ভালভাবে স্কেল করে না যেহেতু টানা মডেলটি স্বয়ংসম্পূর্ণ হওয়া দরকার। যদি সিঙ্ক্রোনাইজ করা মডেলটি অন্য মডেলের উপর নির্ভর করে যা নিজেকে পপুলেট করার জন্য আনা হবে, আগে উল্লেখ করা ভারী ডেটা ব্যবহারের সমস্যাটিকে আরও তাৎপর্যপূর্ণ করা হবে। উপরন্তু, এটি মূল মডেলের সংগ্রহস্থল এবং নেস্টেড মডেলের সংগ্রহস্থলগুলির মধ্যে নির্ভরতা সৃষ্টি করতে পারে।

পুশ-ভিত্তিক সিঙ্ক্রোনাইজেশন

পুশ-ভিত্তিক সিঙ্ক্রোনাইজেশনে, স্থানীয় ডেটা উত্স তার ক্ষমতার সেরা নেটওয়ার্ক ডেটা উত্সের একটি প্রতিরূপ সেট অনুকরণ করার চেষ্টা করে। এটি একটি বেসলাইন সেট করার জন্য প্রথম স্টার্ট-আপে সক্রিয়ভাবে একটি উপযুক্ত পরিমাণ ডেটা নিয়ে আসে, তারপরে যখন সেই ডেটা অচল হয়ে যায় তখন এটি সতর্ক করার জন্য সার্ভার থেকে বিজ্ঞপ্তিগুলির উপর নির্ভর করে।

পুশ-ভিত্তিক সিঙ্ক্রোনাইজেশন
চিত্র 8 : পুশ-ভিত্তিক সিঙ্ক্রোনাইজেশন: ডেটা পরিবর্তিত হলে নেটওয়ার্ক অ্যাপটিকে অবহিত করে এবং পরিবর্তিত ডেটা আনার মাধ্যমে অ্যাপ প্রতিক্রিয়া জানায়

পুরানো বিজ্ঞপ্তি প্রাপ্তির পরে, অ্যাপটি শুধুমাত্র সেই ডেটা আপডেট করার জন্য নেটওয়ার্কের সাথে যোগাযোগ করে যা পুরানো হিসাবে চিহ্নিত করা হয়েছিল। এই কাজটি Repository অর্পণ করা হয় যা নেটওয়ার্ক ডাটা সোর্সের কাছে পৌঁছায় এবং স্থানীয় ডেটা সোর্সে আনা ডেটা টিকে থাকে। যেহেতু রিপোজিটরিটি পর্যবেক্ষণযোগ্য প্রকারের সাথে তার ডেটা প্রকাশ করে, তাই পাঠকদের কোনো পরিবর্তন সম্পর্কে অবহিত করা হবে।

class UserDataRepository(...) {

    suspend fun synchronize() {
        val userData = networkDataSource.fetchUserData()
        localDataSource.saveUserData(userData)
    }
}

এই পদ্ধতিতে, অ্যাপটি নেটওয়ার্ক ডেটা উত্সের উপর অনেক কম নির্ভরশীল এবং এটি বর্ধিত সময়ের জন্য কাজ করতে পারে না। এটি অফলাইনে পড়ার এবং লেখার উভয় অ্যাক্সেস দেয় কারণ এটি ধরে নেওয়া হয় যে এটি স্থানীয়ভাবে নেটওয়ার্ক ডেটা উত্স থেকে সর্বশেষ তথ্য রয়েছে৷

পুশ-ভিত্তিক সিঙ্ক্রোনাইজেশনের সুবিধা এবং অসুবিধাগুলি নীচের সারণীতে সংক্ষিপ্ত করা হয়েছে:

সুবিধাদি অসুবিধা
অ্যাপটি অনির্দিষ্টকালের জন্য অফলাইনে থাকতে পারে। দ্বন্দ্ব সমাধানের জন্য ডেটা সংস্করণ করা অ-তুচ্ছ।
ন্যূনতম ডেটা ব্যবহার। অ্যাপটি শুধুমাত্র পরিবর্তিত ডেটা নিয়ে আসে। সিঙ্ক্রোনাইজেশনের সময় আপনাকে লিখতে উদ্বেগ বিবেচনা করতে হবে।
রিলেশনাল ডেটার জন্য ভাল কাজ করে। প্রতিটি সংগ্রহস্থল শুধুমাত্র এটি সমর্থন করে এমন মডেলের জন্য ডেটা আনার জন্য দায়ী। নেটওয়ার্ক ডেটা উৎসকে সিঙ্ক্রোনাইজেশন সমর্থন করতে হবে।

হাইব্রিড সিঙ্ক্রোনাইজেশন

কিছু অ্যাপ্লিকেশান একটি হাইব্রিড পদ্ধতি ব্যবহার করে যা ডেটার উপর নির্ভর করে টান বা ধাক্কা দেয়। উদাহরণস্বরূপ, ফিড আপডেটের উচ্চ ফ্রিকোয়েন্সির কারণে একটি সোশ্যাল মিডিয়া অ্যাপ চাহিদা অনুযায়ী ব্যবহারকারীর নিম্নলিখিত ফিড আনতে পুল-ভিত্তিক সিঙ্ক্রোনাইজেশন ব্যবহার করতে পারে। একই অ্যাপ সাইন-ইন করা ব্যবহারকারীর ব্যবহারকারীর নাম, প্রোফাইল ছবি এবং আরও অনেক কিছু সহ ডেটার জন্য পুশ-ভিত্তিক সিঙ্ক্রোনাইজেশন ব্যবহার করতে পারে।

শেষ পর্যন্ত, অফলাইন-প্রথম সিঙ্ক্রোনাইজেশন পছন্দ পণ্যের প্রয়োজনীয়তা এবং উপলব্ধ প্রযুক্তিগত অবকাঠামোর উপর নির্ভর করে।

দ্বন্দ্ব সমাধান

যদি অ্যাপটি অফলাইনে স্থানীয়ভাবে ডেটা লেখে যা নেটওয়ার্ক ডেটা উৎসের সাথে ভুলভাবে সংযোজন করা হয়, তাহলে একটি দ্বন্দ্ব দেখা দিয়েছে যা সিঙ্ক্রোনাইজেশন হওয়ার আগে আপনাকে অবশ্যই সমাধান করতে হবে।

দ্বন্দ্ব সমাধানের জন্য প্রায়ই সংস্করণের প্রয়োজন হয়। পরিবর্তনগুলি কখন ঘটেছিল তা ট্র্যাক রাখতে অ্যাপটিকে কিছু হিসাবরক্ষণ করতে হবে। এটি নেটওয়ার্ক ডেটা উৎসে মেটাডেটা পাস করতে সক্ষম করে। নেটওয়ার্ক ডেটা সোর্স তখন সত্যের পরম উৎস প্রদানের দায়িত্ব পালন করে। আবেদনের প্রয়োজনের উপর নির্ভর করে দ্বন্দ্ব সমাধানের জন্য বিবেচনা করার জন্য বিস্তৃত কৌশল রয়েছে। মোবাইল অ্যাপের জন্য একটি সাধারণ পদ্ধতি হল "শেষ লিখতে জয়"।

শেষ লেখা জয়

এই পদ্ধতিতে, ডিভাইসগুলি নেটওয়ার্কে লেখা ডেটার সাথে টাইমস্ট্যাম্প মেটাডেটা সংযুক্ত করে। যখন নেটওয়ার্ক ডেটা উত্স সেগুলি গ্রহণ করে, তখন এটি বর্তমান অবস্থার চেয়ে পুরানো ডেটা বাতিল করে দেয় এবং তার বর্তমান অবস্থার চেয়ে নতুনগুলিকে গ্রহণ করে।

শেষ লেখা দ্বন্দ্ব সমাধানে জয়লাভ করে
চিত্র 9 : "শেষ লেখার জয়" ডেটার জন্য সত্যের উত্স ডেটা লেখার শেষ সত্তা দ্বারা নির্ধারিত হয়

উপরে, উভয় ডিভাইসই অফলাইন এবং প্রাথমিকভাবে নেটওয়ার্ক ডেটা উৎসের সাথে সিঙ্ক করা হয়। অফলাইনে থাকাকালীন, তারা উভয়ই স্থানীয়ভাবে ডেটা লেখে এবং তাদের ডেটা লেখার সময় ট্র্যাক রাখে। যখন তারা উভয়ই অনলাইনে ফিরে আসে এবং নেটওয়ার্ক ডেটা উত্সের সাথে সিঙ্ক্রোনাইজ করে, নেটওয়ার্ক B ডিভাইস থেকে ডেটা বজায় রেখে বিরোধের সমাধান করে কারণ এটি তার ডেটা পরে লিখেছিল।

অফলাইন-প্রথম অ্যাপে ওয়ার্ক ম্যানেজার

উপরে আচ্ছাদিত পঠন এবং লেখার উভয় কৌশলেই, দুটি সাধারণ ইউটিলিটি ছিল:

  • সারি
    • রিডস: নেটওয়ার্ক কানেক্টিভিটি উপলব্ধ না হওয়া পর্যন্ত রিড স্থগিত করতে ব্যবহৃত হয়।
    • লেখা: নেটওয়ার্ক সংযোগ উপলব্ধ না হওয়া পর্যন্ত লেখাগুলিকে স্থগিত করতে এবং পুনরায় চেষ্টার জন্য লেখার অনুরোধ করতে ব্যবহৃত হয়।
  • নেটওয়ার্ক কানেক্টিভিটি মনিটর
    • রিডস: অ্যাপটি সংযুক্ত থাকাকালীন এবং সিঙ্ক্রোনাইজেশনের জন্য রিড কিউ নিষ্কাশন করতে একটি সংকেত হিসাবে ব্যবহৃত হয়
    • লিখেছেন: অ্যাপটি সংযুক্ত থাকাকালীন এবং সিঙ্ক্রোনাইজেশনের জন্য লেখার সারি নিষ্কাশন করতে একটি সংকেত হিসাবে ব্যবহৃত হয়

উভয় ক্ষেত্রেই ওয়ার্ক ম্যানেজার যে অবিরাম কাজ করে তার উদাহরণ। উদাহরণস্বরূপ , Now in Android নমুনা অ্যাপে, স্থানীয় ডেটা উত্স সিঙ্ক্রোনাইজ করার সময় WorkManager একটি রিড কিউ এবং নেটওয়ার্ক মনিটর উভয় হিসাবে ব্যবহৃত হয়৷ স্টার্ট-আপে, অ্যাপটি নিম্নলিখিত কাজগুলি সম্পাদন করে:

  1. স্থানীয় ডেটাসোর্স এবং নেটওয়ার্ক ডেটাসোর্সের মধ্যে সমতা রয়েছে তা নিশ্চিত করার জন্য সারিবদ্ধভাবে পড়া সিঙ্ক্রোনাইজেশন কাজ।
  2. রিড সিঙ্ক্রোনাইজেশন সারি ড্রেন করুন এবং অ্যাপটি অনলাইন হলে সিঙ্ক্রোনাইজ করা শুরু করুন।
  3. সূচকীয় ব্যাকঅফ ব্যবহার করে নেটওয়ার্ক ডেটাসোর্স থেকে একটি পঠন সম্পাদন করুন।
  4. স্থানীয় ডেটাসোর্সে পঠিত ফলাফলগুলিকে স্থির রাখুন যে কোনো দ্বন্দ্বের সমাধান করতে।
  5. অ্যাপের অন্যান্য স্তরগুলি ব্যবহার করার জন্য স্থানীয় ডেটাসোর্স থেকে ডেটা প্রকাশ করুন৷

উপরের চিত্রটি নীচের চিত্রে চিত্রিত করা হয়েছে:

Now in Android অ্যাপে ডেটা সিঙ্ক্রোনাইজেশন
চিত্র 10 : নাও ইন অ্যান্ড্রয়েড অ্যাপে ডেটা সিঙ্ক্রোনাইজেশন

WorkManager- এর সাথে সিঙ্ক্রোনাইজেশন কাজের সারিবদ্ধকরণ KEEP ExistingWorkPolicy এর সাথে এটিকে অনন্য কাজ হিসাবে নির্দিষ্ট করে অনুসরণ করে:

class SyncInitializer : Initializer<Sync> {
   override fun create(context: Context): Sync {
       WorkManager.getInstance(context).apply {
           // Queue sync on app startup and ensure only one
           // sync worker runs at any time
           enqueueUniqueWork(
               SyncWorkName,
               ExistingWorkPolicy.KEEP,
               SyncWorker.startUpSyncWork()
           )
       }
       return Sync
   }
}

যেখানে SyncWorker.startupSyncWork() কে নিম্নলিখিত হিসাবে সংজ্ঞায়িত করা হয়েছে:


/**
 Create a WorkRequest to call the SyncWorker using a DelegatingWorker.
 This allows for dependency injection into the SyncWorker in a different
 module than the app module without having to create a custom WorkManager
 configuration.
*/
fun startUpSyncWork() = OneTimeWorkRequestBuilder<DelegatingWorker>()
    // Run sync as expedited work if the app is able to.
    // If not, it runs as regular work.
   .setExpedited(OutOfQuotaPolicy.RUN_AS_NON_EXPEDITED_WORK_REQUEST)
   .setConstraints(SyncConstraints)
    // Delegate to the SyncWorker.
   .setInputData(SyncWorker::class.delegatedData())
   .build()

val SyncConstraints
   get() = Constraints.Builder()
       .setRequiredNetworkType(NetworkType.CONNECTED)
       .build()

বিশেষত, SyncConstraints দ্বারা সংজ্ঞায়িত Constraints জন্য প্রয়োজন যে NetworkType হতে হবে NetworkType.CONNECTED । অর্থাৎ, এটি চালানোর আগে নেটওয়ার্ক উপলব্ধ না হওয়া পর্যন্ত এটি অপেক্ষা করে।

একবার নেটওয়ার্ক উপলব্ধ হলে, কর্মী উপযুক্ত Repository দৃষ্টান্তে অর্পণ করে SyncWorkName দ্বারা নির্দিষ্ট করা অনন্য কাজের সারিটি নিষ্কাশন করে। যদি সিঙ্ক্রোনাইজেশন ব্যর্থ হয়, doWork() পদ্ধতি Result.retry() দিয়ে ফিরে আসে। WorkManager স্বয়ংক্রিয়ভাবে সূচকীয় ব্যাকঅফের সাথে সিঙ্ক্রোনাইজেশন পুনরায় চেষ্টা করবে। অন্যথায়, এটি Result.success() সিঙ্ক্রোনাইজেশন সম্পূর্ণ করে ফেরত দেয়।

class SyncWorker(...) : CoroutineWorker(appContext, workerParams), Synchronizer {

    override suspend fun doWork(): Result = withContext(ioDispatcher) {
        // First sync the repositories in parallel
        val syncedSuccessfully = awaitAll(
            async { topicRepository.sync() },
            async { authorsRepository.sync() },
            async { newsRepository.sync() },
        ).all { it }

        if (syncedSuccessfully) Result.success()
        else Result.retry()
    }
}

নমুনা

নিম্নলিখিত Google নমুনাগুলি অফলাইন-প্রথম অ্যাপগুলি প্রদর্শন করে৷ অনুশীলনে এই নির্দেশিকা দেখতে তাদের অন্বেষণ করুন:

{% শব্দার্থে %} {% endverbatim %} {% শব্দার্থে %} {% endverbatim %}