Çevrimdışı öncelikli bir uygulama geliştirme

Çevrimdışı öncelikli uygulamalar, internete erişim olmadan temel işlevlerinin tamamını veya önemli bir alt kümesini gerçekleştirebilen bir uygulamadır. Yani iş mantığının bir kısmını veya tamamını çevrimdışı olarak gerçekleştirebilir.

Uygulama verilerine ve iş mantığına erişim sunan veri katmanında çevrimdışı öncelikli bir uygulama başlangıcı oluştururken göz önünde bulundurulması gereken noktalar. Uygulamanın bu verileri zaman zaman cihazın dışındaki kaynaklardan yenilemesi gerekebilir. Bu sırada, güncel kalmak için ağ kaynaklarını çağırması gerekebilir.

Ağ kullanılabilirliği her zaman garanti edilmez. Cihazlarda genellikle ağ bağlantısının bağlantılı veya yavaş olduğu dönemler görülür. Kullanıcılar aşağıdakilerle karşılaşabilir:

  • Sınırlı internet bant genişliği
  • Asansörde veya tünelde olduğu gibi geçiş sırasında yaşanan bağlantı kesintileri.
  • Ara sıra veri erişimi. Örneğin, yalnızca kablosuz ağ kullanan tabletler.

Nedeni ne olursa olsun, bu koşullarda bir uygulamanın düzgün çalışması genellikle mümkündür. Uygulamanızın çevrimdışıyken doğru bir şekilde çalıştığından emin olmak için aşağıdakileri yapabilmesi gerekir:

  • Güvenilir bir ağ bağlantısı olmadan kullanılabilir durumda kalın.
  • İlk ağ aramasının tamamlanmasını veya başarısız olmasını beklemek yerine, kullanıcılara yerel verileri hemen sunun.
  • Verileri, pil ve veri durumuna dikkat ederek getirin. Örneğin, yalnızca şarj veya kablosuz bağlantı gibi optimum koşullarda veri getirme isteğinde bulunabiliriz.

Yukarıdaki ölçütleri karşılayabilen uygulamalara genellikle çevrimdışı öncelikli uygulama denir.

Çevrimdışı öncelikli bir uygulama tasarlama

Çevrimdışı öncelikli bir uygulama tasarlarken işe veri katmanında ve uygulama verileri üzerinde gerçekleştirebileceğiniz iki ana işleme başlamalısınız:

  • Okumalar: Uygulamanın diğer bölümleri tarafından kullanılması için verileri alma (ör. kullanıcıya bilgi görüntüleme).
  • Yazmalar: Kullanıcı girişinin daha sonra alınması için kalıcıdır.

Veri katmanındaki depolar, uygulama verisi sağlamak için veri kaynaklarının birleştirilmesinden sorumludur. Çevrimdışı öncelikli bir uygulamada, en kritik görevlerini gerçekleştirmek için ağ erişimine ihtiyaç duymayan en az bir veri kaynağı olmalıdır. Bu kritik görevlerden biri verileri okumaktır.

Çevrimdışı öncelikli bir uygulamada verileri modelleyin

Çevrimdışı öncelikli bir uygulamada, ağ kaynaklarından yararlanan her depo için en az 2 veri kaynağı bulunur:

  • Yerel veri kaynağı
  • Ağ veri kaynağı
Çevrimdışı öncelikli bir veri katmanı, hem yerel hem de ağ veri kaynaklarından oluşur.
Şekil 1: Çevrimdışı öncelikli bir depo,

Yerel veri kaynağı

Yerel veri kaynağı, uygulamanın standart bilgi kaynağıdır. Uygulamanın üst katmanlarının okuduğu tüm verilerin tek kaynağı olmalıdır. Bu, bağlantı durumları arasında veri tutarlılığı sağlar. Yerel veri kaynağı genellikle diskte tutulan depolama alanıyla desteklenir. Verileri diskte kalıcı olarak tutmak için yaygın olarak kullanılan bazı araçlar şunlardır:

  • Room gibi ilişkisel veritabanları gibi yapılandırılmış veri kaynakları.
  • Yapılandırılmamış veri kaynakları. Örneğin, Datastore ile protokol arabelleğe alır.
  • Basit dosyalar

Ağ veri kaynağı

Ağ veri kaynağı, uygulamanın gerçek durumudur. Yerel veri kaynağı en iyi şekilde ağ veri kaynağıyla senkronize edilir. Ayrıca geride kalabilir. Bu durumda, internete tekrar bağlandığınızda uygulamanın güncellenmesi gerekir. Bunun tersine, ağ veri kaynağı, bağlantı geri geldiğinde uygulama veri kaynağını güncelleyene kadar yerel veri kaynağının gerisinde kalabilir. Uygulamanın alan ve kullanıcı arayüzü katmanları, hiçbir zaman ağ katmanıyla doğrudan ilişki kurmamalıdır. Cihazla iletişim kurmak ve yerel veri kaynağını güncellemek için kullanmak, barındırma repository adlı geliştiricinin sorumluluğundadır.

Kaynakları kullanıma sunma

Yerel veri kaynakları ile ağ veri kaynakları, uygulamanızın bu veri kaynaklarını okuma ve yazma biçimleri açısından temel farklılıklar gösterebilir. Yerel veri kaynağını sorgulamak (SQL sorguları kullanırken olduğu gibi) hızlı ve esnek olabilir. Buna karşılık ağ veri kaynakları yavaş ve kısıtlı olabilir (örneğin, kimliğe göre RESTful kaynaklarına artımlı olarak erişirken). Sonuç olarak, her veri kaynağı genellikle sağladığı verileri kendi temsiline ihtiyaç duyar. Dolayısıyla yerel veri kaynağının ve ağ veri kaynağının kendi modelleri olabilir.

Aşağıdaki dizin yapısı bu kavramı görselleştirmektedir. AuthorEntity, uygulamanın yerel veritabanından okunan bir yazarı temsil eder. NetworkAuthor ise ağ üzerinden serileştirilmiş bir yazarın temsilidir:

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

AuthorEntity ve NetworkAuthor ile ilgili ayrıntılar şu şekildedir:

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

Hem AuthorEntity hem de NetworkAuthor öğelerini veri katmanının içinde tutmak ve harici katmanların tüketmesi için üçüncü bir türü açığa çıkarmak iyi bir uygulamadır. Bu, harici katmanları yerel ve ağ veri kaynaklarında, uygulamanın davranışını temelden değiştirmeyen küçük değişikliklerden korur. Bu, aşağıdaki snippet'te gösterilmektedir:

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

Daha sonra ağ modeli, bunu yerel modele dönüştürmek için bir uzantı yöntemi tanımlayabilir ve yerel model de benzer şekilde bunu aşağıda gösterildiği gibi harici gösterime dönüştürecek bir yönteme sahiptir:

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

Okumalar

Okumalar, çevrimdışı öncelikli bir uygulamada uygulama verileriyle ilgili temel işlemdir. Bu nedenle, uygulamanızın verileri okuyabildiğinden ve yeni veriler sunulur ulaşmaz bunları gösterebildiğinden emin olmanız gerekir. Bunu yapabilen bir uygulama, gözlemlenebilir türlere sahip okuma API'lerini kullanıma sunduğu için reaktif bir uygulamadır.

Aşağıdaki snippet'te OfflineFirstTopicRepository, tüm okunan API'leri için Flows değerini döndürür. Bu şekilde, ağ veri kaynağından güncelleme aldığında okuyucularını güncelleyebilir. Başka bir deyişle, yerel veri kaynağı geçersiz olduğunda OfflineFirstTopicRepository aktarmasının değişmesine izin verir. Bu nedenle, her OfflineFirstTopicRepository okuyucusu, uygulamaya ağ bağlantısı geri yüklendiğinde tetiklenebilecek veri değişikliklerini işlemeye hazır olmalıdır. Ayrıca OfflineFirstTopicRepository, verileri doğrudan yerel veri kaynağından okur. Yalnızca önce yerel veri kaynağını güncelleyerek okuyucularını veri değişiklikleri konusunda bilgilendirebilir.

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

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

Hata işleme stratejileri

Çevrimdışı öncelikli uygulamalarda, meydana gelebilecek veri kaynaklarına bağlı olarak hataları ele almanın benzersiz yolları vardır. Aşağıdaki alt bölümlerde bu stratejiler ana hatlarıyla açıklanmaktadır.

Yerel veri kaynağı

Yerel veri kaynağından okuma sırasında hatalar nadiren görülür. Okuyucuları hatalara karşı korumak için okuyucunun veri topladığı Flows üzerinde catch operatörünü kullanın.

catch operatörünün ViewModel içinde kullanımı aşağıdaki gibidir:

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

Ağ veri kaynağı

Bir ağ veri kaynağından veri okunurken hatalar oluşursa uygulamanın veri getirmeyi yeniden denemek için buluşsal bir yöntem kullanması gerekir. Yaygın buluşsal yöntemler şunları içerir:

Eksponansiyel geri yükleme

Üstel geri yüklemede uygulama, başarılı olana kadar artan zaman aralıklarıyla ağ veri kaynağından veri okumayı denemeye devam eder veya diğer koşullar verinin durması gerektiğini belirtir.

Üstel geri yüklemeyle veri okuma
Şekil 2: Üstel geri yüklemeyle verileri okuma

Uygulamanın geri dönmeye devam edip etmeyeceğini değerlendirme ölçütleri şunlardır:

  • Ağ veri kaynağının belirttiği hata türü. Örneğin, bağlantı eksikliği olduğunu gösteren bir hata döndüren ağ çağrılarını yeniden denemeniz gerekir. Buna karşılık, uygun kimlik bilgileri elde edilene kadar yetkilendirilmeyen HTTP isteklerini yeniden denememelisiniz.
  • İzin verilen maksimum yeniden deneme sayısı.
Ağ bağlantısı izleme

Bu yaklaşımda okuma istekleri, uygulama ağ veri kaynağına bağlanabileceğinden emin olana kadar sıraya alınır. Bağlantı kurulduktan sonra okuma isteği sıraya alınır, veri okunur ve yerel veri kaynağı güncellenir. Android'de bu sıra, bir Room veritabanıyla korunabilir ve WorkManager kullanılarak kalıcı iş olarak boşaltılabilir.

Ağ izleyicileri ve sıralarla veri okuma
Şekil 3: Ağ izleme ile sıra okuma

Yazma işlemleri

Çevrimdışı öncelikli bir uygulamada verileri okumanın önerilen yolu gözlemlenebilir türleri kullanmaktır ancak yazma API'lerinin eşdeğeri, askıya alma işlevleri gibi eşzamansız API'lerdir. Bu, kullanıcı arayüzü iş parçacığının engellenmesini önler ve çevrimdışı öncelikli uygulamalarda yazma işlemleri ağ sınırını aşarken başarısız olabileceği için hataların işlenmesine yardımcı olur.

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

Yukarıdaki snippet'te, yukarıdaki yöntem askıya alındığında tercih edilen eşzamansız API Coroutines'tir.

Stratejileri yazın

Çevrimdışı öncelikli uygulamalarda veri yazarken göz önünde bulundurulması gereken üç strateji vardır. Hangisini seçeceğiniz, yazılan verinin türüne ve uygulamanın gereksinimlerine bağlıdır:

Yalnızca çevrimiçi yazma işlemleri

Verileri ağ sınırı dışına yazmaya çalışın. Başarılı olursa yerel veri kaynağını güncelleyin. Aksi takdirde, istisna oluşturup uygun şekilde yanıt vermesi için arayana bırakın.

Yalnızca online yazmalar
Şekil 4: Yalnızca çevrimiçi yazma işlemleri

Bu strateji genellikle neredeyse gerçek zamanlı olarak online olması gereken yazma işlemleri için kullanılır. Örneğin, banka havalesi. Yazma işlemleri başarısız olabileceğinden genellikle yazma işleminin başarısız olduğunu kullanıcıya bildirmek veya kullanıcının en başta veri yazmayı denemesini önlemek gerekir. Bu senaryolarda uygulayabileceğiniz bazı stratejiler şunlardır:

  • Bir uygulama, veri yazmak için internet erişimi gerektiriyorsa kullanıcıya veri yazma izni veren bir kullanıcı arayüzü sunmamayı tercih edebilir veya en azından uygulamayı devre dışı bırakabilir.
  • Kullanıcının kapatamayacağı bir pop-up mesaj veya geçici bir istem kullanarak kullanıcıya çevrimdışı olduğunu bildirebilirsiniz.

Sıraya alınmış yazma işlemleri

Yazmak istediğiniz bir nesne olduğunda bunu bir sıraya ekleyin. Uygulama tekrar online olduğunda üstel geri çekilerek sırayı boşaltmaya devam edin. Android'de çevrimdışı sıranın boşaltılması kalıcı bir iştir ve genellikle WorkManager'e verilir.

Yeniden deneme içeren sıra yazma
Şekil 5: Yeniden deneme içeren sıra yazma

Aşağıdaki durumlarda bu yaklaşım iyi bir seçimdir:

  • Verilerin ağa yazılması gerekmez.
  • İşlem zamana duyarlı değil.
  • İşlem başarısız olursa kullanıcıya bilgi verilmesi şart değildir.

Bu yaklaşımın kullanım alanları arasında analiz etkinlikleri ve günlük kaydı yer alır.

Tembel yazmalar

Önce yerel veri kaynağına yazın, ardından en kısa sürede ağa bildirmek için yazma işlemini sıraya alın. Uygulama tekrar çevrimiçi olduğunda ağ ile yerel veri kaynakları arasında çakışmalar olabileceğinden, bu çok önemli bir nokta değildir. Anlaşmazlık çözümüyle ilgili bir sonraki bölümde daha ayrıntılı bilgi verilmiştir.

Ağ izleme ile geç yazma
Şekil 6: Geç yazma işlemleri

Bu yaklaşım, veriler uygulama açısından kritik öneme sahip olduğunda doğru seçimdir. Örneğin, çevrimdışı öncelikli bir yapılacaklar listesi uygulamasında, veri kaybı riskini önlemek için kullanıcının çevrimdışına eklediği tüm görevlerin yerel olarak depolanması önemlidir.

Senkronizasyon ve çakışma çözümü

Çevrimdışı öncelikli bir uygulama, bağlantısını tekrar sağladığında yerel veri kaynağındaki verileri ağ veri kaynağındaki verilerle bağdaştırmalıdır. Bu işleme senkronizasyon denir. Bir uygulamanın ağ veri kaynağıyla senkronize edilebileceği iki temel yol vardır:

  • Çekme tabanlı senkronizasyon
  • Push tabanlı senkronizasyon

Çekme tabanlı senkronizasyon

Çekme tabanlı senkronizasyonda, uygulama isteğe bağlı olarak en son uygulama verilerini okumak için ağa ulaşır. Bu yaklaşımda yaygın olarak kullanılan sezgisel yöntemler, gezinme tabanlıdır. Uygulama, verileri sadece kullanıcıya sunmadan hemen önce getirir.

Bu yaklaşım, en çok uygulamada ağ bağlantısının kısa ve ara dönemlerle olmasını beklerken en iyi sonucu verir. Bunun nedeni, veri yenilemenin fırsata dayalı olması ve bağlantı olmamasının uzun süre boyunca kullanıcının eski veya boş bir önbelleğe sahip uygulama hedeflerini ziyaret etmeye çalışma olasılığını artırmasıdır.

Çekme tabanlı senkronizasyon
Şekil 7: Çekme tabanlı senkronizasyon: A cihazı yalnızca A ve B ekranlarının kaynaklarına erişirken B cihazı yalnızca B, C ve D ekranlarının kaynaklarına erişir

Sayfa jetonlarının, belirli bir ekran için sonsuz bir kaydırma listesindeki öğeleri getirmek amacıyla kullanıldığı bir uygulama düşünün. Uygulama, daha sonra ağa geçilebilir, verileri yerel veri kaynağında saklayabilir ve ardından bilgileri kullanıcıya sunmak için yerel veri kaynağından okuyabilir. Ağ bağlantısının olmadığı durumlarda depo, yalnızca yerel veri kaynağından veri isteyebilir. Bu, Jetpack Paging Library tarafından RemoteMediator API'si ile kullanılan kalıptır.

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
}

Pull tabanlı senkronizasyonun avantajları ve dezavantajları aşağıdaki tabloda özetlenmiştir:

Avantajları Dezavantajları
Uygulaması nispeten kolaydır. Yoğun veri kullanımına eğilimli. Bunun nedeni, bir gezinme hedefine yapılan tekrarlanan ziyaretlerin, değiştirilmemiş bilgilerin gereksiz bir şekilde yeniden getirilmesini tetiklemesidir. Doğru şekilde önbelleğe alma yoluyla bu sorunu giderebilirsiniz. Bu işlem, kullanıcı arayüzü katmanında cachedIn operatörüyle veya HTTP önbelleğine sahip ağ katmanında yapılabilir.
İhtiyacınız olmayan veriler hiçbir zaman getirilmez. Alınan modelin kendi kendine yeterli olması gerektiğinden ilişkisel verilerle iyi ölçeklenemez. Senkronize edilen model, kendisini doldurması için getirilen başka modellere bağlıysa daha önce bahsedilen yoğun veri kullanımı sorunu daha da belirgin hale gelir. Ayrıca, üst modelin depoları ile iç içe yerleştirilmiş modelin depoları arasında bağımlılıklara neden olabilir.

Push tabanlı senkronizasyon

Aktarma tabanlı senkronizasyonda, yerel veri kaynağı ağ veri kaynağının en iyi şekilde kopya kümesini taklit etmeye çalışır. Sistem, ilk başlatma sırasında proaktif olarak uygun miktarda veri getirerek bir temel oluşturur. Daha sonra, bu veriler eski olduğunda sunucudan gelen bildirimleri kullanır.

Push tabanlı senkronizasyon
Şekil 8: Push tabanlı senkronizasyon: Ağ, veriler değiştiğinde uygulamayı bilgilendirir ve uygulama, değiştirilen verileri alarak yanıt verir

Eski bildirimi aldıktan sonra, uygulama yalnızca eski olarak işaretlenmiş verileri güncellemek için ağa ulaşır. Bu çalışma, ağ veri kaynağına ulaşan Repository ekibine yetki verir ve yerel veri kaynağına getirilen verileri saklar. Depo, verilerini gözlemlenebilir türlerle sunduğundan okuyuculara değişiklikler hakkında bilgi verilir.

class UserDataRepository(...) {

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

Bu yaklaşımda uygulama, ağ veri kaynağına çok daha az bağımlıdır ve bu veri kaynağı olmadan uzun süre çalışabilir. Yerel olarak ağ veri kaynağından en yeni bilgileri içerdiği varsayıldığı için çevrimdışıyken hem okuma hem de yazma erişimi sunar.

Push tabanlı senkronizasyonun avantajları ve dezavantajları aşağıdaki tabloda özetlenmiştir:

Avantajları Dezavantajları
Uygulama süresiz olarak çevrimdışı kalabilir. Çakışma çözümüne yönelik sürüm verilerinin oluşturulması son derece basit bir işlemdir.
Minimum veri kullanımı. Uygulama yalnızca değiştirilen verileri getirir. Senkronizasyon sırasında yazmayla ilgili endişeleri göz önünde bulundurmanız gerekir.
İlişkisel verilerde iyi sonuç verir. Her depo yalnızca desteklediği modele ait verileri getirmekten sorumludur. Ağ veri kaynağının senkronizasyonu desteklemesi gerekir.

Karma senkronizasyon

Bazı uygulamalar, verilere bağlı olarak çekme veya aktarma uygulanan karma bir yaklaşım kullanır. Örneğin bir sosyal medya uygulaması, feed güncellemesi sıklığının yüksek olması nedeniyle kullanıcının aşağıdaki feed'ini isteğe bağlı olarak getirmek için çekme tabanlı senkronizasyon kullanabilir. Aynı uygulama, oturum açan kullanıcının kullanıcı adı, profil resmi vb. veriler için push tabanlı senkronizasyonu kullanmayı tercih edebilir.

Sonuç olarak, çevrimdışı öncelikli senkronizasyon tercihi ürün gereksinimlerine ve kullanılabilir teknik altyapıya bağlıdır.

Çatışma çözümü

Uygulama çevrimdışıyken ağ veri kaynağıyla uyuşmayan verileri yerel olarak yazıyorsa senkronizasyonun yapılabilmesi için çözmeniz gereken bir çakışma meydana gelmiştir.

Çatışma çözümü genellikle sürüm oluşturmayı gerektirir. Değişikliklerin ne zaman gerçekleştiğini takip etmek için uygulamanın bir miktar muhasebe yapması gerekir. Bu, meta verilerin ağ veri kaynağına aktarılmasını sağlar. Sonrasında ağ veri kaynağı, mutlak doğru kaynağı sağlama sorumluluğuna sahiptir. Uygulamanın ihtiyaçlarına bağlı olarak çatışmaların çözümü için değerlendirilebilecek çok çeşitli stratejiler vardır. Mobil uygulamalar için yaygın yaklaşım "son yazma kazanır" şeklindedir.

Son yazma kazananları

Bu yaklaşımda cihazlar, ağa yazdıkları verilere zaman damgası meta verileri ekler. Ağ veri kaynağı bunları aldığında geçerli durumundan daha eski verileri siler ve geçerli durumundan daha yeni olanları kabul eder.

Son yazma işlemi çakışma çözümünü kazanır
Şekil 9: "Son yazma kazanır" Verilerin doğru kaynağı, veri yazacak son varlık tarafından belirlenir

Yukarıda, her iki cihaz da çevrimdışıdır ve başlangıçta ağ veri kaynağıyla senkronize edilmiştir. Çevrimdışıyken hem verileri yerel olarak yazar hem de verilerini yazdıkları zamanı takip ederler. İkisi de tekrar çevrimiçi olduğunda ve ağ veri kaynağıyla senkronize edildiğinde, ağ, B cihazındaki verileri daha sonra yazdığından bu verileri kalıcı olarak tutarak çakışmayı çözer.

Çevrimdışı öncelikli uygulamalarda WorkManager

Yukarıda bahsedilen okuma ve yazma stratejilerinin ikisinde yaygın olarak görülen iki yardımcı vardır:

  • Sıralar
    • Okumalar: Ağ bağlantısı kullanılabilir hale gelene kadar okumaları ertelemek için kullanılır.
    • Yazma işlemleri: Ağ bağlantısı kullanılabilir olana kadar yazma işlemlerini ertelemek ve yeniden denemeler için yazma işlemlerini sıraya almak amacıyla kullanılır.
  • Ağ bağlantısı izleyicileri
    • Okumalar: Uygulama bağlıyken ve senkronizasyon sırasında okuma sırasını boşaltmak için sinyal olarak kullanılır
    • Yazma işlemleri: Uygulama bağlıyken yazma sırasını boşaltmak ve senkronizasyon için sinyal olarak kullanılır

Her iki durum da WorkManager'ın uzman olduğu kalıcı iş örnekleridir. Örneğin, Now in Android örnek uygulamasında WorkManager, yerel veri kaynağını senkronize ederken hem okuma sırası hem de ağ izleyicisi olarak kullanılır. Uygulama başlangıçta aşağıdaki işlemleri gerçekleştirir:

  1. Okuma senkronizasyonunda, yerel veri kaynağı ile ağ veri kaynağı arasında denklik olduğundan emin olmak için sıraya koyun.
  2. Okuma senkronizasyonu sırasını boşaltın ve uygulama çevrimiçi olduğunda senkronizasyonu başlatın.
  3. Üstel geri yükleme kullanarak ağ veri kaynağından okuma işlemi gerçekleştirin.
  4. Oluşabilecek çakışmaları gidermek için okuma işleminin sonuçlarını yerel veri kaynağında kalıcı olarak tutun.
  5. Yerel veri kaynağındaki verileri, uygulamanın diğer katmanları için de kullanıma sunun.

Yukarıdaki diyagramda gösterilmektedir:

Now in Android uygulamasında veri senkronizasyonu
Şekil 10: Now in Android uygulamasında veri senkronizasyonu

WorkManager ile senkronizasyonu sıraya ekleme işlemi, senkronizasyonu KEEP ExistingWorkPolicy ile benzersiz bir çalışma olarak belirtilerek yapılır:

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

Burada SyncWorker.startupSyncWork() aşağıdaki şekilde tanımlanır:


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

Özellikle SyncConstraints ile tanımlanan Constraints için NetworkType NetworkType.CONNECTED gerekir. Yani, çalışmaya başlamadan önce ağ kullanılabilir hale gelene kadar bekler.

Ağ kullanılabilir olduğunda Çalışan, SyncWorkName tarafından belirlenen benzersiz iş sırasını uygun Repository örneklerine yetki vererek boşaltır. Senkronizasyon başarısız olursa doWork() yöntemi, Result.retry() ile birlikte döndürülür. WorkManager, üstel geri yükleme ile senkronizasyonu otomatik olarak yeniden dener. Aksi takdirde, senkronizasyon tamamlanırken Result.success() döndürülür.

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

Sana Özel

Aşağıdaki Google örneklerinde, çevrimdışına öncelik veren uygulamalar gösterilmektedir. Bu kılavuzu inceleyerek örnekleri inceleyin: