অফলাইন-ফার্স্ট অ্যাপ হল এমন একটি অ্যাপ যা ইন্টারনেট অ্যাক্সেস ছাড়াই তার মূল কার্যকারিতার সবকটি বা একটি গুরুত্বপূর্ণ সাবসেট সম্পাদন করতে সক্ষম। অর্থাৎ, এটি অফলাইনে কিছু বা সব বিজনেস লজিক পারফর্ম করতে পারে।
অফলাইন-ফার্স্ট অ্যাপ তৈরি করার সময় ডেটা লেয়ার থেকে শুরু করতে হয়, যা অ্যাপ্লিকেশন ডেটা ও বিজনেস লজিক অ্যাক্সেস করার সুবিধা দেয়। সময়ে সময়ে, অ্যাপটিকে ডিভাইসের বাইরের সোর্স থেকে এই ডেটা রিফ্রেশ করতে হতে পারে। এটি করার সময়, আপ-টু-ডেট থাকার জন্য নেটওয়ার্ক রিসোর্স কল করার প্রয়োজন হতে পারে।
নেটওয়ার্কের উপলভ্যতা সবসময় নিশ্চিত করা যায় না। ডিভাইসে সাধারণত কিছু সময়ের জন্য নেটওয়ার্ক কানেকশন স্লো বা স্পটি হয়ে যায়। ব্যবহারকারীরা নিম্নলিখিত সমস্যার সম্মুখীন হতে পারেন:
- সীমিত ইন্টারনেট ব্যান্ডউইথ
- কানেকশনে সাময়িক বাধা, যেমন লিফটে বা টানেলে থাকার সময়
- মাঝে মাঝে ডেটা অ্যাক্সেস—যেমন, শুধু ওয়াই-ফাই–এর মাধ্যমে ইন্টারনেটে কানেক্ট করতে পারে এমন ট্যাবলেট
কারণ যাই হোক না কেন, এই পরিস্থিতিতেও কোনও অ্যাপের পক্ষে পর্যাপ্তভাবে কাজ করা প্রায়শই সম্ভব। আপনার অ্যাপ যাতে অফলাইনে সঠিকভাবে কাজ করে তা নিশ্চিত করতে, এটি অবশ্যই নিম্নলিখিত কাজগুলি করতে সক্ষম হতে হবে:
- নির্ভরযোগ্য নেটওয়ার্ক কানেকশন ছাড়াই ব্যবহারযোগ্য থাকে
- প্রথম নেটওয়ার্ক কল সম্পূর্ণ বা ব্যর্থ হওয়ার জন্য অপেক্ষা না করে ব্যবহারকারীদের সাথে সাথে স্থানীয় ডেটা দেখান
- ব্যাটারি ও ডেটার স্ট্যাটাস সম্পর্কে সচেতন থেকে ডেটা ফেচ করুন—যেমন, শুধুমাত্র অনুকূল পরিস্থিতিতে ডেটা ফেচ করার অনুরোধ করে, যেমন চার্জ করার সময় বা ওয়াই-ফাই কানেকশন থাকলে
এইসব শর্ত পূরণ করে এমন অ্যাপকে প্রায়ই অফলাইন-ফার্স্ট অ্যাপ বলা হয়।
অফলাইন-ফার্স্ট অ্যাপ ডিজাইন করা
অফলাইন-ফার্স্ট অ্যাপ ডিজাইন করার সময়, ডেটা লেয়ার থেকে শুরু করুন এবং অ্যাপ ডেটার উপর আপনি দুটি প্রধান অপারেশন করতে পারবেন:
- পড়ে: অ্যাপের অন্যান্য অংশে ব্যবহারের জন্য ডেটা ফিরিয়ে আনা, যেমন ব্যবহারকারীকে তথ্য দেখানো। Compose-এ, আপনি সাধারণত স্টেট পর্যবেক্ষণ করে এটি সম্পন্ন করেন। আপনার UI যখন স্থানীয় ডেটা সোর্সকে স্টেট হিসেবে দেখে, তখন স্ক্রিন অটোমেটিক লেটেস্ট স্থানীয় ডেটা দেখায়।
- Writes: পরে ব্যবহার করার জন্য ব্যবহারকারীর ইনপুট সেভ করে রাখা। Compose-এ, আপনি সাধারণত UI থেকে ViewModel-এ পাঠানো ইভেন্ট ও অ্যাকশন ব্যবহার করে এটি অর্জন করেন।
ডেটা লেয়ারে রিপোজিটরি ডেটা সোর্স একত্রিত করার জন্য দায়ী যাতে অ্যাপ ডেটা প্রদান করা যায়। অফলাইন-ফার্স্ট অ্যাপে, অন্তত একটি ডেটা সোর্স থাকতে হবে যেটির সবচেয়ে গুরুত্বপূর্ণ টাস্ক সম্পূর্ণ করার জন্য নেটওয়ার্ক অ্যাক্সেসের প্রয়োজন নেই। এইসব গুরুত্বপূর্ণ টাস্কের মধ্যে একটি হল ডেটা রিড করা।
অফলাইন-ফার্স্ট অ্যাপে মডেল ডেটা
অফলাইন-ফার্স্ট অ্যাপে নেটওয়ার্ক রিসোর্স ব্যবহার করে এমন প্রতিটি রিপোজিটরির জন্য কমপক্ষে ২টি ডেটা সোর্স থাকে:
- স্থানীয় ডেটা সোর্স
- নেটওয়ার্ক ডেটা সোর্স
স্থানীয় ডেটা সোর্স
স্থানীয় ডেটা সোর্স হল অ্যাপের জন্য ক্যানোনিকাল সত্যের সোর্স। এটি অ্যাপের উচ্চতর লেয়ারের পড়া যেকোনও ডেটার এক্সক্লুসিভ সোর্স হওয়া উচিত। এটি কানেকশন স্ট্যাটাসের মধ্যে ডেটা কনসিস্টেন্সি নিশ্চিত করে। স্থানীয় ডেটা সোর্স প্রায়ই স্টোরেজ দ্বারা ব্যাক-আপ করা হয় যা ডিস্কে সেভ করা থাকে। ডিস্কে ডেটা সেভ করার কিছু সাধারণ উপায় নিচে দেওয়া হল:
- স্ট্রাকচার্ড ডেটা সোর্স, যেমন Room-এর মতো রিলেশনাল ডেটাবেস
- আনস্ট্রাকচার্ড ডেটা সোর্স—যেমন, DataStore-এর সাথে প্রোটোকল বাফার
- সাধারণ ফাইল
নেটওয়ার্ক ডেটা সোর্স
নেটওয়ার্ক ডেটা সোর্স হল অ্যাপ্লিকেশনের আসল অবস্থা। সবচেয়ে ভালো হয়, যদি
লোকাল ডেটা সোর্স নেটওয়ার্ক ডেটা সোর্সের সাথে সিঙ্ক করা থাকে। স্থানীয় ডেটা সোর্স নেটওয়ার্ক ডেটা সোর্সের থেকে পিছিয়ে থাকতে পারে, সেক্ষেত্রে আবার অনলাইন হলে অ্যাপ আপডেট করতে হবে। বিপরীতভাবে, নেটওয়ার্ক ডেটা সোর্স স্থানীয় ডেটা সোর্সের থেকে পিছিয়ে থাকতে পারে, যতক্ষণ না কানেক্টিভিটি ফিরে আসার পরে অ্যাপ এটি আপডেট করতে পারে। অ্যাপের ডোমেন ও UI লেয়ার কখনই নেটওয়ার্ক লেয়ারের সাথে সরাসরি যোগাযোগ
করবে না। হোস্টিং repository-এর দায়িত্ব হল
এটির সাথে যোগাযোগ করা এবং লোকাল ডেটা সোর্স আপডেট করার জন্য এটি ব্যবহার করা।
কাস্টম বনাম ম্যানেজ করা সিঙ্ক্রোনাইজেশন
অফলাইন-ফার্স্ট ডেটা লেয়ার তৈরি করার দুটি প্রাথমিক উপায় আছে:
- কাস্টম সিঙ্ক স্ট্যাক: আপনি লোকাল স্টোরেজে Room বা ফাইলের মাধ্যমে লোকাল ডেটাবেস বজায় রাখেন, কাস্টম নেটওয়ার্ক ক্লায়েন্ট তৈরি করেন এবং WorkManager-এর মাধ্যমে রিকনসিলিয়েশন ওয়ার্কার শিডিউল করেন। এই পদ্ধতিটি স্কিমা এবং কাস্টম দ্বন্দ্ব সমাধান অ্যালগরিদমের উপর সর্বাধিক নিয়ন্ত্রণ প্রদান করে।
- ম্যানেজ করা ক্লাউড পরিষেবা: Cloud Firestore-এর মতো সমাধান অল-ইন-ওয়ান লোকাল ক্যাশে এবং রিয়েল-টাইম ক্লাউড স্ট্রিম প্রদান করতে পারে। SDK ডিস্ক ক্যাশিং, অফলাইন রাইট কিউয়িং এবং দ্বিমুখী সিঙ্ক অটোমেটিক ম্যানেজ করে, আপনার রিপোজিটরিতে সরাসরি রিয়েল-টাইম ডেটা স্ট্রিম ডেলিভার করে। রিলেশনাল PostgreSQL ব্যাকএন্ডের জন্য, Firebase SQL Connect টাইপ-সেফ Kotlin ক্লায়েন্ট SDK তৈরি করে যা আপনার রিপোজিটরির সাথে নির্বিঘ্নে ইন্টিগ্রেট করে। আনস্ট্রাকচার্ড ডেটার (যেমন, মিডিয়া, বড় ফাইল বা ব্যাক-আপ) জন্য, Firebase-এর জন্য ক্লাউড স্টোরেজ আপলোড ও ডাউনলোড অপারেশন সহ ক্লাউড-হোস্টেড অবজেক্ট স্টোরেজ প্রদান করে যা নেটওয়ার্ক বিঘ্নিত হলে অটোমেটিক পজ ও আবার চালু হয়।
রিসোর্স এক্সপোজ করা
আপনার অ্যাপ কীভাবে লোকাল ও নেটওয়ার্ক ডেটা সোর্স থেকে ডেটা পড়তে ও লিখতে পারে, তার মধ্যে মৌলিক পার্থক্য থাকতে পারে। লোকাল ডেটা সোর্স কোয়েরি করা দ্রুত ও নমনীয় হতে পারে, যেমন 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,
)
পড়ে
অফলাইন-ফার্স্ট অ্যাপে অ্যাপ ডেটার উপর রিড হল মৌলিক অপারেশন। তাই আপনাকে অবশ্যই নিশ্চিত করতে হবে যে আপনার অ্যাপ ডেটা পড়তে পারে এবং নতুন ডেটা উপলভ্য হলেই অ্যাপ তা দেখাতে পারে। যে অ্যাপ এটি করতে পারে সেটি হল রিঅ্যাক্টিভ অ্যাপ কারণ এটি পর্যবেক্ষণযোগ্য প্রকারের সাথে রিড API প্রকাশ করে।
নিচের স্নিপেটে, OfflineFirstTopicRepository এর সমস্ত রিড API-এর জন্য Flow রিটার্ন করে। এর ফলে, নেটওয়ার্ক ডেটা সোর্স থেকে আপডেট পেলে,
এটি তার পাঠকদের আপডেট করতে পারে। অন্যভাবে বলতে গেলে, এটি
OfflineFirstTopicRepository-কে পরিবর্তন পুশ করতে দেয় যখন এর স্থানীয় ডেটা সোর্স
অবৈধ হয়ে যায়। তাই, OfflineFirstTopicRepository-এর প্রতিটি রিডারকে
অ্যাপে নেটওয়ার্ক কানেক্টিভিটি রিস্টোর করার সময় ট্রিগার হতে পারে এমন ডেটা পরিবর্তন ম্যানেজ করার জন্য
প্রস্তুত থাকতে হবে। এছাড়াও, OfflineFirstTopicRepository সরাসরি লোকাল ডেটা সোর্স থেকে
ডেটা রিড করে। এটি শুধুমাত্র তার স্থানীয় ডেটা সোর্স প্রথমে আপডেট করে ডেটা পরিবর্তনের বিষয়ে তার পাঠকদের জানাতে পারে।
class TopicsViewModel(
offlineFirstTopicsRepository: OfflineFirstTopicsRepository
) : ViewModel() {
val topics: StateFlow<List<Topic>> = offlineFirstTopicsRepository.getTopicsStream()
.stateIn(
scope = viewModelScope,
started = SharingStarted.WhileSubscribed(5_000),
initialValue = emptyList()
)
}
Jetpack Compose অ্যাপে, ডেটা লেয়ার ও UI-এর মধ্যে সংযোগ স্থাপন করতে ViewModel ব্যবহার করুন।
ViewModel-এ, stateIn অপারেটর ব্যবহার করে Flow-কে StateFlow-এ
কনভার্ট করুন। তারপর কম্পোজেবল
collectAsStateWithLifecycle() ব্যবহার করে সেইসব স্টেট সংগ্রহ করে এবং অটোমেটিক লাইফসাইকেল-অ্যাওয়ার পদ্ধতিতে সাবস্ক্রিপশন ম্যানেজ করে।
collectAsStateWithLifecycle() সম্পর্কে আরও তথ্যের জন্য,
State ও Jetpack Compose দেখুন।
সমস্যা ম্যানেজ করার স্ট্র্যাটেজি
অফলাইন-ফার্স্ট অ্যাপে সমস্যা সমাধানের অনন্য উপায় আছে, যা নির্ভর করে ডেটা সোর্সের উপর যেখানে সমস্যা হতে পারে। নিম্নলিখিত উপবিভাগে এইসব স্ট্র্যাটেজির আউটলাইন দেওয়া হয়েছে।
স্থানীয় ডেটা সোর্স
স্থানীয় ডেটা সোর্স থেকে পড়ার সময় সমস্যা কমানোর চেষ্টা করুন। পাঠকদের
ভুল থেকে রক্ষা করতে, Flow-এ catch অপারেটর ব্যবহার করুন যেখান থেকে
পাঠক ডেটা সংগ্রহ করছে।
আপনি ViewModel-এ catch অপারেটর এইভাবে ব্যবহার করতে পারবেন:
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()) }
}
আরও স্থিতিস্থাপক পদ্ধতির জন্য, LCE (কন্টেন্ট লোড করার সময় হওয়া সমস্যা) সমাধান বিবেচনা করুন। LCE-তে, পড়ার সময় কোনও সমস্যা হলে, আপনি সমস্যা সংক্রান্ত স্ট্যাটাস দেখান। সাধারণত, Kotlin সিল করা ক্লাস হিসেবে UI স্টেট মডেলিং করে আপনি LCE অর্জন করেন।
// Define the LCE UI state
sealed interface AuthorUiState {
data object Loading : AuthorUiState
data class Success(val author: Author) : AuthorUiState
data object Error : AuthorUiState
}
class AuthorViewModel(
authorsRepository: AuthorsRepository,
...
) : ViewModel() {
private val authorId: String = ...
// Observe author information and map to LCE state
val authorUiState: StateFlow<AuthorUiState> =
authorsRepository.getAuthorStream(id = authorId)
.map<Author, AuthorUiState> { author ->
AuthorUiState.Success(author)
}
.catch { emit(AuthorUiState.Error) }
.stateIn(
scope = viewModelScope,
started = SharingStarted.WhileSubscribed(5_000),
initialValue = AuthorUiState.Loading
)
}
নেটওয়ার্ক ডেটা সোর্স
নেটওয়ার্ক ডেটা সোর্স থেকে ডেটা পড়ার সময় কোনও সমস্যা হলে, ডেটা ফেচ করার জন্য অ্যাপকে হিউরিস্টিক পদ্ধতি ব্যবহার করতে হবে। সাধারণ হিউরিস্টিকসের মধ্যে নিম্নলিখিতগুলি অন্তর্ভুক্ত:
এক্সপোনেনশিয়াল ব্যাকঅফ
এক্সপোনেনশিয়াল ব্যাকঅফ-এ, অ্যাপটি নেটওয়ার্ক ডেটা সোর্স থেকে পড়ার চেষ্টা চালিয়ে যায়, যতক্ষণ না এটি সফল হয় বা অন্যান্য শর্তের কারণে এটি বন্ধ করতে হয়, ততক্ষণ সময়ের ব্যবধান বাড়তে থাকে।
অ্যাপটি ব্যাক অফ করা চালিয়ে যাচ্ছে কিনা তা মূল্যায়ন করার মাপকাঠিতে নিম্নলিখিতগুলি অন্তর্ভুক্ত রয়েছে:
- নেটওয়ার্ক ডেটা সোর্স যে ধরনের সমস্যার ইঙ্গিত করেছে। যেমন, নেটওয়ার্ক কল আবার করার চেষ্টা করুন, যা কানেক্টিভিটির অভাবের কারণে সমস্যা দেখায়। সঠিক ক্রেডেনশিয়াল উপলভ্য না হওয়া পর্যন্ত, অনুমোদিত নয় এমন HTTP অনুরোধ আবার করার চেষ্টা করবেন না।
- সর্বাধিক যতবার চেষ্টা করা যাবে।
নেটওয়ার্ক কানেক্টিভিটি মনিটর করা
এই পদ্ধতিতে, অ্যাপটি নেটওয়ার্ক ডেটা সোর্সের সাথে কানেক্ট করতে পারবে কিনা তা নিশ্চিত না হওয়া পর্যন্ত পড়ার অনুরোধগুলি সারিবদ্ধ করা হয়। কানেকশন তৈরি হয়ে গেলে, পড়ার অনুরোধ ডিকিউ করা হয়, ডেটা পড়া হয় এবং স্থানীয় ডেটা সোর্স আপডেট করা হয়। Android-এ এই সারি Room ডেটাবেসের মাধ্যমে ম্যানেজ করা হতে পারে এবং WorkManager ব্যবহার করে পারসিস্টেন্ট কাজ হিসেবে ড্রেন করা হতে পারে।
লেখেন
অফলাইন-ফার্স্ট অ্যাপে ডেটা পড়ার জন্য সাজেস্ট করা পদ্ধতি হল পর্যবেক্ষণযোগ্য প্রকার ব্যবহার করা, রাইট API-এর সমতুল্য হল অ্যাসিঙ্ক্রোনাস API যেমন সাসপেন্ড ফাংশন। এটি UI থ্রেড ব্লক করা এড়ায় এবং সমস্যা সমাধান করতে সাহায্য করে কারণ অফলাইন-ফার্স্ট অ্যাপে লেখার সময় নেটওয়ার্ক বাউন্ডারি ক্রস করলে তা ব্যর্থ হতে পারে।
interface UserDataRepository {
/**
* Updates the bookmarked status for a news resource
*/
suspend fun updateNewsResourceBookmark(newsResourceId: String, bookmarked: Boolean)
}
আগের স্নিপেটে, পদ্ধতিটি সাসপেন্ড হওয়ার কারণে, পছন্দের অ্যাসিঙ্ক্রোনাস API হল Coroutines।
কৌশল ঠিক করা
অফলাইন-ফার্স্ট অ্যাপে ডেটা লেখার সময়, তিনটি স্ট্র্যাটেজি বিবেচনা করতে হবে। আপনি কোনটি বেছে নেবেন তা নির্ভর করে কী ধরনের ডেটা লেখা হচ্ছে এবং অ্যাপের প্রয়োজনীয়তার উপর:
শুধুমাত্র অনলাইনে লেখা
নেটওয়ার্ক বাউন্ডারি জুড়ে ডেটা লেখার চেষ্টা করা। সফল হলে, স্থানীয় ডেটা সোর্স আপডেট করুন; অন্যথায়, ব্যতিক্রম থ্রো করুন এবং কলারের উপর ছেড়ে দিন যাতে তিনি যথাযথ উত্তর দিতে পারেন।
এই স্ট্র্যাটেজি প্রায়ই রাইট ট্রানজ্যাকশনের জন্য ব্যবহার করা হয় যা প্রায় রিয়েল টাইমে অনলাইনে হতে হবে—যেমন, ব্যাঙ্ক ট্রান্সফার। যেহেতু রাইট করার সময় সমস্যা হতে পারে, তাই রাইট করার সময় সমস্যা হয়েছে বলে ব্যবহারকারীকে জানানো অথবা ব্যবহারকারীকে প্রথম থেকেই ডেটা রাইট করার চেষ্টা করা থেকে বিরত রাখা প্রয়োজন। এইসব পরিস্থিতিতে আপনি কিছু কৌশল প্রয়োগ করতে পারেন:
- কোনও অ্যাপের ডেটা লেখার জন্য ইন্টারনেট অ্যাক্সেসের প্রয়োজন হলে, আপনি ব্যবহারকারীকে ডেটা লেখার অনুমতি দেয় এমন UI না দেখানোর বিকল্প বেছে নিতে পারেন অথবা অন্ততপক্ষে এটি বন্ধ করে দিতে পারেন।
- ব্যবহারকারী অফলাইনে আছেন তা জানাতে আপনি এমন একটি
AlertDialogব্যবহার করতে পারেন যা ব্যবহারকারী বাতিল করতে পারবেন না অথবা একটিSnackbarব্যবহার করতে পারেন।
সারিবদ্ধ রাইট
আপনি লিখতে চান এমন কোনও অবজেক্ট থাকলে, সেটি একটি সারিতে যোগ করুন। অ্যাপটি আবার অনলাইন হলে, এক্সপোনেনশিয়াল ব্যাকঅফ সহ সারিটি খালি করুন। Android-এ, অফলাইন সারি খালি করা হল একটি স্থায়ী কাজ যা প্রায়শই WorkManager-কে প্রতিনিধি হিসেবে দেওয়া হয়।
নিম্নলিখিত পরিস্থিতিতে এই পদ্ধতিটি একটি ভালো বিকল্প:
- নেটওয়ার্কে ডেটা লিখতে হবে এমন কোনও বাধ্যবাধকতা নেই।
- ট্রানজ্যাকশনটি সময় সংবেদনশীল নয়।
- অপারেশনটি ব্যর্থ হলে ব্যবহারকারীকে জানানো আবশ্যক নয়।
এই পদ্ধতির ব্যবহারের ক্ষেত্রে অ্যানালিটিক্স ইভেন্ট ও লগিং অন্তর্ভুক্ত।
লেজি রাইট
প্রথমে লোকাল ডেটা সোর্সে লিখুন, তারপর নেটওয়ার্ককে যত তাড়াতাড়ি সম্ভব জানানোর জন্য লেখার কাজটি সারিবদ্ধ করুন। এটি গুরুত্বপূর্ণ কারণ অ্যাপটি আবার অনলাইন হলে নেটওয়ার্ক এবং স্থানীয় ডেটা সোর্সের মধ্যে বিরোধ হতে পারে। সমস্যা সমাধানের পরবর্তী বিভাগে আরও বিবরণ দেওয়া আছে।
অ্যাপের জন্য ডেটা অত্যন্ত গুরুত্বপূর্ণ হলে এই পদ্ধতিটি সঠিক পছন্দ। উদাহরণস্বরূপ, অফলাইন-ফার্স্ট টু-ডু লিস্ট অ্যাপে, ব্যবহারকারী অফলাইনে যোগ করা যেকোনও টাস্ক স্থানীয়ভাবে স্টোর করা অত্যন্ত গুরুত্বপূর্ণ যাতে ডেটা হারানোর ঝুঁকি এড়ানো যায়।
সিঙ্ক্রোনাইজেশন ও দ্বন্দ্ব সমাধান
অফলাইন-ফার্স্ট অ্যাপ কানেক্টিভিটি রিস্টোর করলে, সেটিকে লোকাল ডেটা সোর্সের ডেটা নেটওয়ার্ক ডেটা সোর্সের ডেটার সাথে মিলিয়ে নিতে হয়। এই প্রসেসকে সিঙ্ক্রোনাইজেশন বলা হয়। কোনও অ্যাপ তার নেটওয়ার্ক ডেটা সোর্সের সাথে দুটি প্রধান উপায়ে সিঙ্ক করতে পারে:
- পুল-ভিত্তিক সিঙ্ক্রোনাইজেশন
- পুশ-ভিত্তিক সিঙ্ক্রোনাইজেশন
পুল-ভিত্তিক সিঙ্ক্রোনাইজেশন
পুল-ভিত্তিক সিঙ্ক্রোনাইজেশনে, অ্যাপটি নেটওয়ার্কে পৌঁছে যায় চাহিদা অনুযায়ী লেটেস্ট অ্যাপ্লিকেশন ডেটা পড়ে। এই পদ্ধতির জন্য একটি সাধারণ হিউরিস্টিক হল নেভিগেশন ভিত্তিক, যেখানে অ্যাপটি ব্যবহারকারীর কাছে উপস্থাপন করার ঠিক আগে ডেটা নিয়ে আসে।
এই পদ্ধতিটি সবচেয়ে ভাল কাজ করে যখন অ্যাপটি সংক্ষিপ্ত থেকে মধ্যবর্তী সময়ের জন্য কোনও নেটওয়ার্ক কানেক্টিভিটি আশা করে না। এর কারণ হল, ডেটা রিফ্রেশ করার সুযোগ সবসময় পাওয়া যায় না এবং দীর্ঘ সময় কানেক্টিভিটি না থাকলে, ব্যবহারকারী পুরনো বা খালি ক্যাশে নিয়ে অ্যাপের ডেস্টিনেশন ভিজিট করার চেষ্টা করতে পারেন।
এমন একটি অ্যাপের কথা ভাবুন যেখানে কোনও নির্দিষ্ট স্ক্রিনের জন্য অন্তহীন স্ক্রলিং তালিকায় আইটেম নিয়ে আসার জন্য পৃষ্ঠা টোকেন ব্যবহার করা হয়। ইমপ্লিমেন্টেশনটি নেটওয়ার্কে পৌঁছাতে, স্থানীয় ডেটা সোর্সে ডেটা সেভ করতে এবং তারপর স্থানীয় ডেটা সোর্স থেকে ডেটা পড়ে ব্যবহারকারীকে তথ্য দেখাতে দেরি করতে পারে। নেটওয়ার্ক কানেক্টিভিটি না থাকলে, রিপোজিটরি শুধু স্থানীয় ডেটা সোর্স থেকে ডেটার জন্য অনুরোধ করতে পারে। এটি হল সেই প্যাটার্ন যা
Jetpack Paging Library তার 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
}
পুল-ভিত্তিক সিঙ্ক্রোনাইজেশনের সুবিধা এবং অসুবিধাগুলি নিম্নলিখিত সারণীতে সংক্ষিপ্ত করা হয়েছে:
| সুবিধা | অসুবিধা |
|---|---|
| তুলনামূলকভাবে প্রয়োগ করা সহজ। | প্রচুর ডেটা ব্যবহার করতে পারে। কারণ, কোনও নেভিগেশন গন্তব্যে বারবার গেলে অপরিবর্তিত তথ্য অকারণে রিফেচ করা হয়। আপনি সঠিক ক্যাশিংয়ের মাধ্যমে এটি প্রশমিত করতে পারেন। এটি UI লেয়ারে cachedIn অপারেটর অথবা নেটওয়ার্ক লেয়ারে HTTP ক্যাশের মাধ্যমে করা যেতে পারে। |
| যে ডেটা প্রয়োজন নেই তা কখনও ফেচ করা হয় না। | রিলেশনাল ডেটার সাথে ভালভাবে স্কেল করে না কারণ পুল করা মডেলটি স্বয়ংসম্পূর্ণ হতে হবে। সিঙ্ক করা মডেলটি যদি নিজেকে পপুলেট করার জন্য অন্যান্য মডেলের উপর নির্ভর করে, তাহলে আগে উল্লেখ করা ডেটার অত্যধিক ব্যবহারের সমস্যাটি আরও গুরুতর হয়ে ওঠে। এছাড়াও, এটি পেরেন্ট মডেলের রিপোজিটরি এবং নেস্টেড মডেলের রিপোজিটরির মধ্যে ডিপেন্ডেন্সি তৈরি করতে পারে। |
পুশ-ভিত্তিক সিঙ্ক্রোনাইজেশন
পুশ-ভিত্তিক সিঙ্ক্রোনাইজেশনে, লোকাল ডেটা সোর্স নেটওয়ার্ক ডেটা সোর্সের রেপ্লিকা সেট তৈরি করার যথাসাধ্য চেষ্টা করে। এটি প্রথমবার চালু করার সময় বেসলাইন সেট করার জন্য উপযুক্ত পরিমাণ ডেটা আগে থেকেই নিয়ে নেয়। তারপরে, ডেটা পুরনো হয়ে গেলে, সার্ভার থেকে বিজ্ঞপ্তি পাওয়ার উপর এটি নির্ভর করে।
পুরনো বিজ্ঞপ্তি পাওয়ার পরে, অ্যাপটি নেটওয়ার্কের সাথে যোগাযোগ করে
শুধুমাত্র পুরনো হিসেবে চিহ্নিত ডেটা আপডেট করে। এই কাজটি
Repository-কে ডেলিগেট করা হয়, যা নেটওয়ার্ক ডেটা সোর্সের সাথে যোগাযোগ করে এবং লোকাল ডেটা সোর্স থেকে
ফেচ করা ডেটা সেভ করে রাখে। যেহেতু রিপোজিটরি তার ডেটা পর্যবেক্ষণযোগ্য ধরনের সাথে প্রকাশ করে, তাই পাঠকদের যেকোনও পরিবর্তন সম্পর্কে জানানো হয়।
class UserDataRepository(...) {
suspend fun synchronize() {
val userData = networkDataSource.fetchUserData()
localDataSource.saveUserData(userData)
}
}
এই পদ্ধতিতে, অ্যাপটি নেটওয়ার্ক ডেটা সোর্সের উপর অনেক কম নির্ভর করে এবং এটি ছাড়া দীর্ঘ সময় ধরে কাজ করতে পারে। এটি অফলাইন থাকাকালীন পড়া ও লেখা, দুটিরই অ্যাক্সেস অফার করে, কারণ এটি ধরে নেয় যে নেটওয়ার্ক ডেটা সোর্স থেকে লেটেস্ট তথ্য লোকাল স্টোরেজে রয়েছে।
পুশ-ভিত্তিক সিঙ্ক্রোনাইজেশনের সুবিধা এবং অসুবিধাগুলি নিম্নলিখিত সারণীতে সংক্ষিপ্ত করা হয়েছে:
| সুবিধা | অসুবিধা |
|---|---|
| অ্যাপটি অনির্দিষ্টকালের জন্য অফলাইনে থাকতে পারে। | সমস্যা সমাধানের জন্য ভার্সনিং ডেটা গুরুত্বপূর্ণ। |
| সবচেয়ে কম ডেটা ব্যবহার। অ্যাপটি শুধুমাত্র পরিবর্তিত ডেটা নিয়ে আসে। | সিঙ্ক করার সময় আপনাকে রাইট কনসার্ন বিবেচনা করতে হবে। |
| রিলেশনাল ডেটার জন্য ভালো কাজ করে। প্রতিটি রিপোজিটরি শুধুমাত্র সেই মডেলের জন্য ডেটা আনার জন্য দায়ী যা এটি সাপোর্ট করে। | নেটওয়ার্ক ডেটা সোর্সে সিঙ্ক করার সুবিধা থাকতে হবে। |
হাইব্রিড সিঙ্ক্রোনাইজেশন
কিছু অ্যাপ হাইব্রিড পদ্ধতি ব্যবহার করে যা ডেটার উপর নির্ভর করে পুল- বা পুশ-ভিত্তিক হয়। যেমন, কোনও সোশ্যাল মিডিয়া অ্যাপ, ফিড ঘন ঘন আপডেট হওয়ার কারণে, ব্যবহারকারীর চাহিদা অনুযায়ী ফলোয়িং ফিড ফেচ করতে পুল-ভিত্তিক সিঙ্ক্রোনাইজেশন ব্যবহার করতে পারে। একই অ্যাপ সাইন-ইন করা ব্যবহারকারীর ডেটার জন্য পুশ-ভিত্তিক সিঙ্ক্রোনাইজেশন ব্যবহার করার বিকল্প বেছে নিতে পারে, যার মধ্যে তার ইউজারনেম, প্রোফাইল ছবি এবং আরও অনেক কিছু অন্তর্ভুক্ত।
শেষ পর্যন্ত, অফলাইন-ফার্স্ট সিঙ্ক করার বিকল্প প্রোডাক্টের প্রয়োজনীয়তা ও উপলভ্য প্রযুক্তিগত পরিকাঠামোর উপর নির্ভর করে।
দ্বন্দ্ব সমাধান
অফলাইন থাকাকালীন, অ্যাপটি যদি স্থানীয়ভাবে ডেটা লেখে যা নেটওয়ার্ক ডেটা সোর্সের সাথে সামঞ্জস্যপূর্ণ নয়, তাহলে সিঙ্ক করার আগে আপনাকে অবশ্যই এই সমস্যার সমাধান করতে হবে।
সমস্যা সমাধানের জন্য প্রায়ই ভার্সনিংয়ের প্রয়োজন হয়। কখন পরিবর্তন হয়েছে তা ট্র্যাক করার জন্য অ্যাপকে কিছু বুককিপিং করতে হবে, যাতে এটি নেটওয়ার্ক ডেটা সোর্সে মেটাডেটা পাস করতে পারে। তারপর, নেটওয়ার্ক ডেটা সোর্সের দায়িত্ব হল সত্যের পরম সোর্স প্রদান করা। অ্যাপ্লিকেশনের প্রয়োজনীয়তার উপর নির্ভর করে বিরোধ সমাধানের জন্য অনেক কৌশল বিবেচনা করা যেতে পারে। মোবাইল অ্যাপের ক্ষেত্রে, "শেষবার যা লেখা হয়েছে সেটিই সঠিক" পদ্ধতিটি সাধারণত ব্যবহার করা হয়।
শেষবার লেখা ডেটা জেতে
এই পদ্ধতিতে, ডিভাইসগুলি নেটওয়ার্কে যে ডেটা লেখে তার সাথে টাইমস্ট্যাম্প মেটাডেটা যোগ করে। নেটওয়ার্ক ডেটা সোর্স সেগুলি পেলে, বর্তমান অবস্থার চেয়ে পুরনো ডেটা বাতিল করে দেয় এবং বর্তমান অবস্থার চেয়ে নতুন ডেটা গ্রহণ করে।
ছবি ৯-এ, দুটি ডিভাইসই অফলাইন এবং প্রাথমিকভাবে নেটওয়ার্ক ডেটা সোর্সের সাথে সিঙ্ক করা হয়েছে। অফলাইন থাকাকালীন, তারা দুজনেই স্থানীয়ভাবে ডেটা লেখেন এবং ডেটা লেখার সময় ট্র্যাক করেন। দুটি ডিভাইস আবার অনলাইন হয়ে নেটওয়ার্ক ডেটা সোর্সের সাথে সিঙ্ক করলে, নেটওয়ার্ক ডিভাইস B-এর ডেটা রেখে দিয়ে সমস্যার সমাধান করে, কারণ ডিভাইস B পরে ডেটা লিখেছে।
অফলাইন-ফার্স্ট অ্যাপে WorkManager
আগে উল্লেখ করা রিড ও রাইট স্ট্র্যাটেজিতে দুটি সাধারণ ইউটিলিটি আছে:
- সারিতে আছে
- রিড: নেটওয়ার্ক কানেক্টিভিটি উপলভ্য না হওয়া পর্যন্ত রিড স্থগিত করার জন্য ব্যবহার করা হয়।
- Writes: নেটওয়ার্ক কানেক্টিভিটি উপলভ্য না হওয়া পর্যন্ত রাইট স্থগিত রাখতে এবং আবার চেষ্টা করার জন্য রাইটকে আবার সারিতে রাখতে ব্যবহার করা হয়।
- নেটওয়ার্ক কানেক্টিভিটি মনিটর
- পড়া: অ্যাপ কানেক্ট করা থাকলে, পড়ার সারি খালি করার জন্য এবং সিঙ্ক করার জন্য সিগন্যাল হিসেবে ব্যবহার করা হয়।
- রাইট: অ্যাপ কানেক্ট করা থাকলে রাইট কিউ ড্রেন করার জন্য এবং সিঙ্ক করার জন্য সিগন্যাল হিসেবে ব্যবহার করা হয়।
দুটি ক্ষেত্রেই পারসিস্টেন্ট কাজ-এর উদাহরণ দেওয়া হয়েছে যা WorkManager খুব ভালোভাবে করতে পারে।
যেমন, Now in Android স্যাম্পেল অ্যাপে, লোকাল ডেটা সোর্স সিঙ্ক করার সময় WorkManager-কে রিড কিউ ও নেটওয়ার্ক মনিটর, উভয় হিসেবেই ব্যবহার করা হয়। স্টার্টআপে, অ্যাপটি নিম্নলিখিত কাজগুলি করে:
- লোকাল ডেটা সোর্স ও নেটওয়ার্ক ডেটা সোর্সের মধ্যে সমতা বজায় রাখতে, সিঙ্ক করার কাজ সারিবদ্ধ করে।
- অ্যাপ অনলাইন হলে, সিঙ্ক করার জন্য অপেক্ষারত রিড অপারেশনগুলি শেষ করে এবং সিঙ্ক করা শুরু করে।
- এক্সপোনেনশিয়াল ব্যাকঅফ ব্যবহার করে নেটওয়ার্ক ডেটা সোর্স থেকে ডেটা রিড করে।
- লোকাল ডেটা সোর্সে রিড করার ফলাফল সেভ করে এবং কোনও সমস্যা হলে তা সমাধান করে।
- অ্যাপের অন্যান্য লেয়ারের ব্যবহারের জন্য লোকাল ডেটা সোর্স থেকে ডেটা প্রকাশ করে।
এইসব অ্যাকশন নিম্নলিখিত ডায়াগ্রামে ব্যাখ্যা করা হয়েছে:
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 হতে হবে। অর্থাৎ, নেটওয়ার্ক উপলভ্য না হওয়া পর্যন্ত
এটি অপেক্ষা করে।
নেটওয়ার্ক উপলভ্য হয়ে গেলে, Worker উপযুক্ত 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 স্যাম্পেল অফলাইন-ফার্স্ট অ্যাপের ডেমো দেখায়। এই নির্দেশিকা বাস্তবে প্রয়োগ করতে, সেগুলি এক্সপ্লোর করুন:
আপনার জন্য সাজেস্ট করা হয়েছে
- মনে রাখবেন: জাভাস্ক্রিপ্ট বন্ধ থাকলে লিঙ্ক টেক্সট দেখানো হয়
- UI স্টেট প্রোডাকশন
- UI লেয়ার
- ডেটা লেয়ার