Membangun aplikasi offline-first

Aplikasi offline-first adalah aplikasi yang dapat melakukan semuanya atau subset penting dalam fungsi intinya, tanpa akses ke internet. Artinya, aplikasi dapat menjalankan sebagian atau semua logika bisnisnya secara offline.

Sebaiknya Anda memulai pembuatan aplikasi offline-first dari lapisan data yang menawarkan akses ke data aplikasi dan logika bisnis. Aplikasi mungkin perlu memperbarui data ini dari waktu ke waktu dari sumber di luar perangkat. Untuk melakukannya, Anda mungkin perlu memanggil resource jaringan agar tetap mendapatkan update terbaru.

Ketersediaan jaringan tidak selalu dijamin. Perangkat biasanya memiliki periode koneksi jaringan yang tidak stabil atau lambat. Pengguna mungkin mengalami hal berikut:

  • Bandwidth internet terbatas.
  • Gangguan koneksi sementara, seperti saat berada di elevator atau terowongan.
  • Akses data terputus-putus. Misalnya, tablet khusus Wi-Fi.

Apa pun alasannya, aplikasi sering kali dapat berfungsi secara memadai dalam situasi ini. Untuk memastikan aplikasi Anda berfungsi dengan benar secara offline, aplikasi harus dapat melakukan hal berikut:

  • Dapat terus digunakan tanpa koneksi jaringan yang andal.
  • Menampilkan data lokal kepada pengguna secara langsung, bukan menunggu panggilan jaringan pertama selesai atau gagal.
  • Mengambil data dengan cara yang mengenali status data dan baterai. Misalnya, dengan hanya meminta pengambilan data dalam kondisi yang optimal, seperti saat mengisi daya atau terhubung ke Wi-Fi.

Aplikasi yang dapat memenuhi kriteria di atas sering disebut aplikasi offline-first.

Mendesain aplikasi offline-first

Saat mendesain aplikasi offline-first, Anda harus memulai dari lapisan data dan dua operasi utama yang dapat Anda lakukan pada data aplikasi:

  • Operasi baca: Mengambil data untuk digunakan oleh bagian lain aplikasi seperti menampilkan informasi kepada pengguna.
  • Operasi tulis: Menyimpan input pengguna untuk pengambilan nanti.

Repositori di lapisan data bertanggung jawab untuk menggabungkan sumber data guna menyediakan data aplikasi. Dalam aplikasi offline-first, setidaknya harus ada satu sumber data yang tidak memerlukan akses jaringan untuk melakukan tugas yang paling penting. Salah satu tugas penting ini adalah membaca data.

Data model di aplikasi offline-first

Aplikasi offline-first memiliki minimal 2 sumber data untuk setiap repositori yang menggunakan resource jaringan:

  • Sumber data lokal
  • Sumber data jaringan
Lapisan data offline-first terdiri dari sumber data lokal dan jaringan
Gambar 1: Repositori offline-first

Sumber data lokal

Sumber data lokal adalah sumber tepercaya kanonis untuk aplikasi. Sumber ini harus menjadi sumber eksklusif setiap data yang dibaca oleh lapisan aplikasi yang lebih tinggi. Hal ini memastikan konsistensi data di antara status koneksi. Sumber data lokal sering kali didukung oleh penyimpanan yang disimpan ke disk. Beberapa cara umum untuk menyimpan data ke disk adalah sebagai berikut:

  • Sumber data terstruktur, seperti database relasional, seperti Room.
  • Sumber data tidak terstruktur. Misalnya, buffering protokol dengan Datastore.
  • File sederhana

Sumber data jaringan

Sumber data jaringan adalah status aplikasi yang sebenarnya. Sumber data lokal paling baik disinkronkan dengan sumber data jaringan. Sumber ini juga dapat mengalami lag, sehingga aplikasi perlu diupdate saat kembali online. Sebaliknya, sumber data jaringan mungkin mengalami lag di belakang sumber data lokal hingga aplikasi dapat mengupdatenya saat kembali terhubung ke internet. Lapisan domain dan UI aplikasi tidak boleh bekerja sama dengan lapisan jaringan secara langsung. Host repository bertanggung jawab untuk berkomunikasi dengannya, dan menggunakannya untuk mengupdate sumber data lokal.

Mengekspos resource

Sumber data lokal dan jaringan dapat memiliki perbedaan secara mendasar terkait cara aplikasi membaca dan menulis ke sumber data tersebut. Membuat kueri sumber data lokal dapat dilakukan dengan cepat dan fleksibel, misalnya saat menggunakan kueri SQL. Sebaliknya, sumber data jaringan dapat menjadi lambat dan dibatasi, seperti saat mengakses resource RESTful secara bertahap menurut ID. Akibatnya, setiap sumber data sering kali memerlukan representasi tersendiri untuk data yang diberikan. Oleh karena itu, sumber data lokal dan sumber data jaringan mungkin memiliki modelnya sendiri.

Struktur direktori di bawah memvisualisasikan konsep ini. AuthorEntity adalah representasi penulis yang dibaca dari database lokal aplikasi, dan NetworkAuthor adalah representasi penulis yang diserialisasi melalui jaringan:

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

Detail AuthorEntity dan NetworkAuthor berikut:

/**
 * 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,
)

Sebaiknya simpan AuthorEntity dan NetworkAuthor secara internal ke lapisan data dan ekspos jenis ketiga yang dapat digunakan oleh lapisan eksternal. Tindakan ini melindungi lapisan eksternal dari perubahan kecil pada sumber data lokal dan jaringan yang tidak mengubah perilaku aplikasi secara mendasar. Hal ini ditunjukkan dalam cuplikan berikut:

/**
 * 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,
)

Model jaringan kemudian dapat menentukan metode ekstensi untuk mengonversinya menjadi model lokal, dan model lokal juga dapat menentukan metode untuk mengonversinya menjadi representasi eksternal seperti yang ditunjukkan di bawah ini:

/**
 * 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,
)

Operasi baca

Operasi baca adalah operasi mendasar pada data aplikasi di aplikasi offline-first. Oleh karena itu, Anda harus memastikan bahwa aplikasi dapat membaca data, dan segera menampilkan data baru begitu tersedia. Aplikasi yang dapat melakukannya adalah aplikasi reaktif karena mengekspos API baca dengan jenis observable.

Dalam cuplikan di bawah ini, OfflineFirstTopicRepository menampilkan Flows untuk semua API bacanya. Hal ini memungkinkan repositori mengupdate pembacanya saat menerima update dari sumber data jaringan. Dengan kata lain, repositori memungkinkan push OfflineFirstTopicRepository berubah saat sumber data lokalnya dibatalkan. Oleh karena itu, setiap pembaca OfflineFirstTopicRepository harus disiapkan untuk menangani perubahan data yang dapat dipicu saat konektivitas jaringan dipulihkan ke aplikasi. Selain itu, OfflineFirstTopicRepository akan membaca data secara langsung dari sumber data lokal. Elemen ini hanya dapat memberi tahu pembacanya tentang perubahan data dengan mengupdate sumber data lokalnya terlebih dahulu.

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

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

Strategi penanganan error

Ada cara unik untuk menangani error di aplikasi offline-first, bergantung pada sumber data tempat error tersebut terjadi. Sub-bagian berikut menguraikan strategi ini.

Sumber data lokal

Error saat membaca dari sumber data lokal akan jarang terjadi. Untuk melindungi pembaca dari error, gunakan operator catch pada Flows tempat pembaca mengumpulkan data.

Penggunaan operator catch di ViewModel adalah sebagai berikut:

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()) }
}

Sumber data jaringan

Jika terjadi error saat membaca data dari sumber data jaringan, aplikasi harus menggunakan heuristik untuk mencoba mengambil data lagi. Heuristik umum meliputi:

Backoff eksponensial

Dalam backoff eksponensial, aplikasi terus mencoba membaca dari sumber data jaringan dengan interval waktu yang meningkat hingga berhasil, atau kondisi lain yang menentukan bahwa aplikasi harus berhenti.

Membaca data dengan backoff eksponensial
Gambar 2: Membaca data dengan backoff eksponensial

Kriteria untuk mengevaluasi apakah aplikasi harus tetap melakukan backoff meliputi:

  • Jenis error yang ditunjukkan oleh sumber data jaringan. Misalnya, Anda harus mencoba lagi panggilan jaringan yang menampilkan error kurangnya konektivitas. Sebaliknya, Anda tidak boleh mencoba lagi permintaan HTTP yang tidak diotorisasi hingga kredensial yang tepat tersedia.
  • Percobaan ulang maksimum yang diperbolehkan.
Pemantauan konektivitas jaringan

Dalam pendekatan ini, permintaan baca dimasukkan ke dalam antrean sampai aplikasi yakin dapat terhubung ke sumber data jaringan. Setelah koneksi dibuat, permintaan baca kemudian dikeluarkan dari antrean, data dibaca, dan sumber data lokal diupdate. Di Android, antrean ini dapat dikelola dengan database Room, dan dikosongkan sebagai tugas persisten menggunakan WorkManager.

Membaca data dengan antrean dan pemantauan jaringan
Gambar 3: Membaca antrean dengan pemantauan jaringan

Operasi tulis

Cara yang direkomendasikan untuk membaca data dalam aplikasi offline-first adalah menggunakan jenis observable, sedangkan untuk API tulis adalah API asinkron seperti fungsi penangguhan. Hal ini menghindari pemblokiran UI thread, dan membantu penanganan error karena operasi tulis di aplikasi offline-first dapat gagal saat melintasi batas jaringan.

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

Dalam cuplikan di atas, pilihan API asinkron adalah Coroutine karena metode di atas ditangguhkan.

Strategi operasi tulis

Ketika menulis data di aplikasi offline-first, ada tiga strategi yang perlu dipertimbangkan. Pilihan Anda bergantung pada jenis data yang ditulis dan persyaratan aplikasi:

Operasi tulis khusus online

Cobalah untuk menulis data melintasi batas jaringan. Jika berhasil, update sumber data lokal. Jika tidak, berikan pengecualian dan biarkan pemanggil merespons dengan tepat.

Operasi tulis khusus online
Gambar 4: Operasi tulis khusus online

Strategi ini sering digunakan untuk transaksi tulis yang harus terjadi secara online hampir secara real time. Misalnya, transfer bank. Karena operasi tulis mungkin gagal, sering kali perlu dikomunikasikan kepada pengguna bahwa operasi tulis gagal, atau cegah pengguna untuk mencoba menulis data sejak awal. Beberapa strategi yang dapat Anda gunakan dalam skenario ini dapat mencakup:

  • Jika aplikasi memerlukan akses internet untuk menulis data, aplikasi dapat memilih untuk tidak menampilkan UI kepada pengguna yang memungkinkan pengguna menulis data, atau setidaknya menonaktifkannya.
  • Anda dapat menggunakan pesan pop-up yang tidak dapat ditutup oleh pengguna, atau perintah sementara, untuk memberi tahu pengguna bahwa mereka sedang offline.

Operasi tulis yang diantrekan

Jika Anda memiliki objek yang ingin ditulis, masukkan ke antrean. Lanjutkan untuk menghabiskan antrean dengan backoff eksponensial saat aplikasi kembali online. Di Android, menghabiskan antrean offline adalah pekerjaan persisten yang sering didelegasikan ke WorkManager.

Antrean operasi tulis dengan percobaan ulang
Gambar 5: Antrean operasi tulis dengan percobaan ulang

Pendekatan ini adalah pilihan yang baik jika:

  • Data tidak harus ditulis ke jaringan.
  • Transaksi tidak peka terhadap waktu.
  • Pengguna tidak perlu diberi tahu jika operasi gagal.

Kasus penggunaan untuk pendekatan ini meliputi peristiwa analisis dan logging.

Operasi tulis lambat

Tulis ke sumber data lokal terlebih dahulu, lalu antrekan operasi tulis tersebut untuk memberi tahu jaringan sesegera mungkin. Hal ini tidaklah mudah karena bisa terjadi konflik antara jaringan dan sumber data lokal saat aplikasi kembali online. Bagian berikutnya tentang resolusi konflik akan memberikan detail selengkapnya.

Operasi tulis lambat dengan pemantauan jaringan
Gambar 6: Operasi tulis lambat

Pendekatan ini merupakan pilihan yang tepat saat data sangat dibutuhkan oleh aplikasi. Misalnya, dalam aplikasi daftar tugas offline-first, setiap tugas yang ditambahkan pengguna secara offline disimpan secara lokal untuk menghindari risiko kehilangan data.

Sinkronisasi dan resolusi konflik

Saat memulihkan konektivitasnya, aplikasi offline-first harus merekonsiliasi data di sumber data lokalnya dengan data di sumber data jaringan. Proses ini disebut sinkronisasi. Ada dua cara utama untuk menyinkronkan aplikasi dengan sumber data jaringannya:

  • Sinkronisasi berbasis pull
  • Sinkronisasi berbasis push

Sinkronisasi berbasis pull

Dalam sinkronisasi berbasis pull, aplikasi akan terhubung ke jaringan untuk membaca data aplikasi terbaru secara on demand. Heuristik umum untuk pendekatan ini adalah berbasis navigasi. Artinya, aplikasi hanya mengambil data tepat sebelum menampilkannya kepada pengguna.

Pendekatan ini berfungsi optimal jika aplikasi mengharapkan periode singkat hingga menengah tanpa konektivitas jaringan. Hal ini karena refresh data bersifat oportunistik, dan tidak adanya konektivitas dalam waktu lama meningkatkan peluang bahwa pengguna mencoba membuka tujuan aplikasi dengan cache yang sudah tidak berlaku atau kosong.

Sinkronisasi berbasis pull
Gambar 7: Sinkronisasi berbasis pull: Perangkat A mengakses resource hanya untuk layar A dan B, sedangkan perangkat B mengakses resource hanya untuk layar B, C, dan D

Pertimbangkan aplikasi tempat token halaman digunakan untuk mengambil item dalam daftar scroll tanpa akhir untuk layar tertentu. Implementasi dapat menjangkau jaringan dengan lambat, mempertahankan data ke sumber data lokal, lalu membaca dari sumber data lokal untuk menampilkan informasi kembali kepada pengguna. Jika tidak ada konektivitas jaringan, repositori dapat meminta data dari sumber data lokal saja. Ini adalah pola yang digunakan oleh Library Paging Jetpack dengan RemoteMediator API-nya.

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
}

Kelebihan dan kekurangan sinkronisasi berbasis pull dirangkum dalam tabel di bawah:

Kelebihan Kekurangan
Relatif mudah diterapkan. Rentan dengan penggunaan data yang berat. Hal ini karena kunjungan berulang ke tujuan navigasi akan memicu pengambilan kembali informasi yang tidak perlu. Anda dapat melakukan mitigasi dengan penyimpanan cache yang sesuai. Hal ini dapat dilakukan di lapisan UI dengan operator cachedIn, atau di lapisan jaringan dengan cache HTTP.
Data yang tidak diperlukan tidak akan pernah diambil. Tidak diskalakan dengan baik dengan data relasional karena model yang di-pull harus dapat mengisi sendiri. Jika model yang disinkronkan bergantung pada model lain yang akan diambil untuk mengisi dirinya sendiri, masalah penggunaan data berat yang disebutkan sebelumnya akan menjadi lebih signifikan. Selain itu, hal ini dapat menyebabkan repositori model induk dan repositori model bertingkat saling bergantung.

Sinkronisasi berbasis push

Dalam sinkronisasi berbasis push, sumber data lokal mencoba meniru set replika sumber data jaringan sebaik mungkin. Sumber data lokal secara proaktif mengambil jumlah data yang sesuai pada saat pertama kali dimulai untuk menetapkan dasar pengukuran, setelah itu mengandalkan notifikasi dari server untuk memperingatkannya saat data tersebut sudah tidak berlaku.

Sinkronisasi berbasis push
Gambar 8: Sinkronisasi berbasis push: Jaringan memberi tahu aplikasi saat data berubah dan aplikasi merespons dengan mengambil perubahan data

Setelah menerima notifikasi yang sudah tidak berlaku, aplikasi menghubungi jaringan untuk hanya mengupdate data yang ditandai sebagai tidak berlaku. Pekerjaan ini didelegasikan ke Repository yang menjangkau sumber data jaringan, dan menyimpan data yang diambil ke sumber data lokal. Karena repositori mengekspos datanya dengan jenis observable, pembaca akan diberi tahu jika ada perubahan.

class UserDataRepository(...) {

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

Dalam pendekatan ini, aplikasi menjadi semakin tidak bergantung pada sumber data jaringan dan dapat berfungsi tanpanya untuk jangka waktu yang lama. Pendekatan ini menawarkan akses baca dan tulis saat offline karena dianggap memiliki informasi terbaru dari sumber data jaringan secara lokal.

Kelebihan dan kekurangan sinkronisasi berbasis push dirangkum dalam tabel di bawah ini:

Kelebihan Kekurangan
Aplikasi dapat tetap offline tanpa batas waktu. Pembuatan versi data untuk resolusi konflik tidaklah mudah.
Penggunaan data minimum. Aplikasi hanya mengambil data yang telah diubah. Anda perlu mempertimbangkan masalah operasi tulis selama sinkronisasi.
Berfungsi baik untuk data relasional. Setiap repositori hanya bertanggung jawab mengambil data untuk model yang didukungnya. Sumber data jaringan harus mendukung sinkronisasi.

Sinkronisasi campuran

Beberapa aplikasi menggunakan pendekatan campuran yang berbasis pull atau push, bergantung pada data. Misalnya, aplikasi media sosial dapat menggunakan sinkronisasi berbasis pull untuk mengambil feed "mengikuti" sesuai permintaan pengguna karena tingginya frekuensi update feed. Aplikasi yang sama dapat memilih untuk menggunakan sinkronisasi berbasis push untuk data tentang pengguna yang login, termasuk nama pengguna, foto profil, dan sebagainya.

Pada akhirnya, pilihan sinkronisasi offline-first bergantung pada persyaratan produk dan infrastruktur teknis yang tersedia.

Resolusi konflik

Jika aplikasi menulis data secara lokal saat offline namun tidak sesuai dengan sumber data jaringan, konflik yang terjadi harus Anda selesaikan sebelum sinkronisasi dapat dilakukan.

Resolusi konflik sering kali memerlukan pembuatan versi. Aplikasi perlu melakukan beberapa pembukuan untuk mencatat kapan perubahan terjadi. Hal ini memungkinkan penerusan metadata ke sumber data jaringan. Sumber data jaringan memiliki tanggung jawab untuk menyediakan sumber tepercaya absolut. Ada beragam strategi yang dapat dipertimbangkan untuk menyelesaikan konflik, bergantung pada kebutuhan aplikasi. Untuk aplikasi seluler, pendekatan yang umum digunakan adalah "last write wins".

Last write wins

Dalam pendekatan ini, perangkat melampirkan metadata stempel waktu ke data yang ditulis ke jaringan. Saat menerima data, sumber data jaringan akan menghapus data yang lebih lama dari statusnya saat ini dan menerima yang lebih baru dari statusnya saat ini.

Resolusi konflik last write wins
Gambar 9: "Last write wins" Sumber tepercaya data ditentukan oleh entity terakhir yang akan menulis data

Di atas, kedua perangkat sedang offline dan awalnya disinkronkan dengan sumber data jaringan. Saat offline, kedua perangkat menulis data secara lokal dan mencatat waktu penulisan data. Saat kembali online dan menyinkronkan dengan sumber data jaringan, jaringan akan menyelesaikan konflik dengan menyimpan data dari perangkat B karena data tersebut ditulis paling baru.

WorkManager di aplikasi offline-first

Dalam strategi operasi baca dan tulis yang dibahas di atas, ada dua utilitas umum:

  • Antrean
    • Operasi baca: Digunakan untuk menunda operasi baca hingga konektivitas jaringan tersedia.
    • Operasi tulis: Digunakan untuk menunda operasi tulis hingga konektivitas jaringan tersedia, dan mengantrekan ulang operasi tulis untuk percobaan ulang.
  • Pemantau konektivitas jaringan
    • Operasi baca: Digunakan sebagai sinyal untuk menghabiskan antrean operasi baca saat aplikasi terhubung dan untuk sinkronisasi
    • Operasi tulis: Digunakan sebagai sinyal untuk menghabiskan antrean operasi tulis saat aplikasi terhubung dan untuk sinkronisasi

Kedua kasus tersebut adalah contoh pekerjaan persisten yang merupakan keunggulan dari WorkManager. Misalnya dalam aplikasi contoh Now in Android, WorkManager digunakan sebagai antrean operasi baca dan pemantauan jaringan saat menyinkronkan sumber data lokal. Saat start-up, aplikasi akan melakukan tindakan berikut:

  1. Mengantrekan pekerjaan sinkronisasi operasi baca untuk memastikan adanya kesetaraan antara sumber data lokal dan sumber data jaringan.
  2. Menghabiskan antrean sinkronisasi operasi baca dan memulai sinkronisasi saat aplikasi sedang online.
  3. Melakukan operasi baca dari sumber data jaringan menggunakan backoff eksponensial.
  4. Menyimpan hasil operasi baca ke sumber data lokal untuk menyelesaikan konflik yang mungkin terjadi.
  5. Mengekspos data dari sumber data lokal untuk digunakan oleh lapisan aplikasi lainnya.

Hal di atas diilustrasikan dalam diagram di bawah ini:

Sinkronisasi data di aplikasi Now in Android
Gambar 10: Sinkronisasi data di aplikasi Now in Android

Antrean pekerjaan sinkronisasi dengan WorkManager diikuti dengan menentukannya sebagai pekerjaan unik dengan 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
   }
}

Dengan SyncWorker.startupSyncWork() ditentukan sebagai berikut:


/**
 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()

Secara khusus, Constraints yang ditentukan oleh SyncConstraints mengharuskan NetworkType menjadi NetworkType.CONNECTED. Artinya, elemen ini menunggu hingga jaringan tersedia sebelum berjalan.

Setelah jaringan tersedia, Pekerja akan menghabiskan antrean pekerjaan unik yang ditentukan oleh SyncWorkName dengan mendelegasikan ke instance Repository yang sesuai. Jika sinkronisasi gagal, metode doWork() akan ditampilkan dengan Result.retry(). WorkManager akan otomatis mencoba ulang sinkronisasi dengan backoff eksponensial. Jika tidak, metode ini akan menampilkan Result.success() yang menyelesaikan sinkronisasi.

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()
    }
}

Contoh

Contoh Google berikut menunjukkan aplikasi offline-first. Jelajahi untuk melihat panduan ini dalam praktik: