Offline-orientierte App erstellen

Eine Offline-First-App ist eine App, die alle oder eine kritische Teilmenge ausführen kann ohne Internetzugang über die Hauptfunktionalität zu informieren. Das heißt, es kann und die gesamte Geschäftslogik offline auszuführen.

Überlegungen beim Erstellen einer Offline-First-App in der Datenschicht die Zugriff auf Anwendungsdaten und Geschäftslogik bietet. Die App muss möglicherweise diese Daten gelegentlich aus geräteexternen Quellen zu aktualisieren. In Dabei werden unter Umständen Netzwerkressourcen benötigt, um auf dem neuesten Stand zu bleiben.

Die Netzwerkverfügbarkeit kann nicht immer garantiert werden. Geräte haben häufig Zeiträume, in denen eine langsame oder langsame Netzwerkverbindung. Folgendes kann auftreten:

  • Begrenzte Internetbandbreite
  • Vorübergehende Verbindungsunterbrechungen, z. B. in einem Aufzug oder Tunnel.
  • Gelegentlicher Datenzugriff. Dazu gehören beispielsweise Tablets, die ausschließlich über WLAN genutzt werden.

Unabhängig vom Grund ist es oft möglich, dass eine App ordnungsgemäß funktioniert. unter diesen Umständen zu erreichen. Damit Ihre App auch offline ordnungsgemäß funktioniert, sollte Ihnen Folgendes ermöglichen:

  • Auch ohne stabile Netzwerkverbindung nutzbar
  • Nutzern lokale Daten sofort zur Verfügung stellen, anstatt auf die ersten zu warten abgeschlossen oder fehlgeschlagen ist.
  • Rufen Sie Daten unter Beachtung des Akku- und Datenstatus ab. Für indem Sie beispielsweise nur Datenabrufe unter optimalen Bedingungen anfordern, wie beim Laden oder im WLAN.

Eine App, die die oben genannten Kriterien erfüllt, wird oft als Offline-First-App bezeichnet.

Eine Offline-App entwerfen

Beim Entwerfen einer Offline-First-App sollten Sie mit der Datenschicht beginnen und Die beiden Hauptoperationen, die Sie für Anwendungsdaten durchführen können:

  • Lesevorgänge: Abrufen von Daten zur Verwendung durch andere Teile der App, z. B. Anzeigen Informationen für die Nutzenden zu liefern.
  • Schreibvorgänge: Nutzereingaben zum späteren Abrufen beibehalten.

Repositories in der Datenschicht sind dafür verantwortlich, dass Datenquellen kombiniert werden um App-Daten bereitzustellen. In einer Offline-First-App muss es mindestens ein Datenelement geben Quelle, die zur Ausführung ihrer wichtigsten Aufgaben keinen Netzwerkzugriff benötigt. Eins ist das Lesen von Daten.

Daten in einer Offline-First-App modellieren

Eine Offline-First-App hat mindestens zwei Datenquellen für jedes Repository, nutzt Netzwerkressourcen:

  • Die lokale Datenquelle
  • Datenquelle des Netzwerks
<ph type="x-smartling-placeholder">
</ph> Eine Offline-First-Datenschicht besteht aus lokalen und Netzwerkdatenquellen.
Abbildung 1: Ein Offline-First-Repository

Die lokale Datenquelle

Die lokale Datenquelle ist die kanonische Source of Truth für die App. Es sollte die ausschließliche Quelle aller Daten sein, die von höheren Ebenen der App gelesen werden. Dadurch wird die Datenkonsistenz zwischen Verbindungsstatus sichergestellt. Die lokalen Daten Quelle wird oft durch Speicher abgesichert, der auf dem Laufwerk gespeichert ist. Übliche Mittel der Speicherung von Daten auf der Festplatte sind:

  • Strukturierte Datenquellen wie relationale Datenbanken wie Room
  • Unstrukturierte Datenquellen. Protokollzwischenspeicherung für Datastore.
  • Einfache Dateien

Datenquelle des Netzwerks

Die Netzwerkdatenquelle ist der tatsächliche Status der Anwendung. Die lokalen Daten Quelle am besten mit der Netzwerkdatenquelle synchronisiert ist. Sie kann auch verzögert im Hintergrund. In diesem Fall muss die App aktualisiert werden, sobald sie wieder online ist. Umgekehrt kann die Netzwerkdatenquelle hinter der lokalen Datenquelle zurückbleiben, bis kann die App sie aktualisieren, sobald wieder eine Verbindung besteht. Die Domain- und UI-Ebenen der App darf niemals direkt mit der Netzwerkschicht in Verbindung stehen. Es handelt sich um die für die Kommunikation mit dem Host (repository) verantwortlich ist. aktualisieren Sie die lokale Datenquelle.

Ressourcen freigeben

Die lokalen und Netzwerk-Datenquellen können sich in der Art und Weise, wie Ihre App in ihnen lesen und schreiben. Das Abfragen einer lokalen Datenquelle kann schnell und flexibel sein. z. B. bei SQL-Abfragen. Umgekehrt können Netzwerkdatenquellen langsam eingeschränkt, z. B. beim inkrementellen Zugriff auf RESTful-Ressourcen nach ID. Als dass jede Datenquelle häufig eine eigene Darstellung der Daten benötigt, . Für die lokale Datenquelle und die Netzwerkdatenquelle werden daher möglicherweise eigenen Modellen.

Die folgende Verzeichnisstruktur veranschaulicht dieses Konzept. AuthorEntity ist ein Darstellung eines Autors, der aus der lokalen Datenbank der App gelesen wurde, und der NetworkAuthor ist eine Darstellung eines Autors, der über das Netzwerk serialisiert ist:

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

Im Folgenden finden Sie die Details zu AuthorEntity und 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,
)

Es empfiehlt sich, sowohl AuthorEntity als auch NetworkAuthor beizubehalten. und einen dritten Typ für externe Schichten verbrauchen. Dadurch werden externe Ebenen vor geringfügigen Änderungen in den lokalen und Netzwerkdatenquellen, die das Verhalten der App nicht grundlegend verändern. Dies wird im folgenden Snippet gezeigt:

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

Das Netzwerkmodell kann dann eine Erweiterungsmethode definieren, um sie in die lokale und das lokale Modell hat ebenfalls ein Modell, das es in das externe Modell Darstellung wie unten dargestellt:

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

Lesevorgänge

Lesevorgänge sind die grundlegende Operation von App-Daten in einer Offline-First-App. Ich muss deshalb dafür gesorgt werden, dass Ihre App die Daten lesen kann und dass, sobald neue Daten verfügbar sind, die von der App angezeigt werden können. Eine App, die dies kann, ist ein reaktiver app enthalten, weil sie Lese-APIs mit beobachtbaren Typen verfügbar machen.

Im folgenden Snippet gibt OfflineFirstTopicRepository Flows für alle seine Lese-APIs. Dadurch kann sie ihre Leser aktualisieren, wenn sie Updates erhält Netzwerkdatenquelle an. Mit anderen Worten: Es ermöglicht OfflineFirstTopicRepository-Push-Änderungen, wenn die lokale Datenquelle lautet für ungültig erklärt. Daher muss jeder Leser der OfflineFirstTopicRepository auf Datenänderungen vorbereitet sein, die ausgelöst werden können, wenn die Netzwerkverbindung wird in der App wiederhergestellt. Außerdem liest OfflineFirstTopicRepository Daten direkt aus der lokalen Datenquelle. Sie kann nur seine Leser über Daten benachrichtigen indem Sie zuerst die lokale Datenquelle aktualisieren.

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

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

Strategien zur Fehlerbehandlung

Es gibt verschiedene Möglichkeiten, Fehler in Offline-First-Apps zu beheben. in welchen Datenquellen sie auftreten können. In den folgenden Unterabschnitten werden diese zu entwickeln.

Lokale Datenquelle

Fehler beim Lesen aus der lokalen Datenquelle sollten selten auftreten. Zum Schutz Lesern vor Fehlern den Operator catch für die Flows verwenden, das Lesen von Daten.

Der Operator catch wird in einer ViewModel so verwendet:

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

Netzwerkdatenquelle

Wenn beim Lesen von Daten aus einer Netzwerkdatenquelle Fehler auftreten, muss die App eine Heuristik verwenden, um das Abrufen von Daten zu wiederholen. Zu den gängigen Heuristiken gehören:

Exponentieller Backoff

Beim exponentiellen Backoff versucht die App, Daten aus die Netzwerkdatenquelle mit zunehmenden Zeitintervallen, bis sie erfolgreich ist, oder andere Bedingungen vorschreiben, dass er beendet werden sollte.

<ph type="x-smartling-placeholder">
</ph> Daten mit exponentiellem Backoff lesen
Abbildung 2: Daten mit exponentiellem Backoff lesen

Kriterien, anhand derer du beurteilen kannst, ob die App dauerhaft deaktiviert werden sollte:

  • Die Art des Fehlers, der von der Netzwerkdatenquelle angegeben wurde. Zum Beispiel sollten Sie Netzwerkaufrufe wiederholen, die einen Fehler zurückgeben, der auf einen Mangel an Konnektivität haben. Umgekehrt sollten Sie keine HTTP-Anfragen wiederholen, bis die richtigen Anmeldedaten verfügbar sind.
  • Maximal zulässige Wiederholungsversuche.
Monitoring der Netzwerkverbindung

Bei diesem Ansatz werden Leseanfragen so lange in die Warteschlange gestellt, bis die Anwendung sicher ist, dass sie eine Verbindung zur Netzwerkdatenquelle herstellen. Sobald eine Verbindung hergestellt wurde, Dann wird die Leseanfrage aus der Warteschlange entfernt, die gelesenen Daten und die lokale Datenquelle aktualisiert. Unter Android kann diese Warteschlange mit einer Raumdatenbank verwaltet und wie folgt beendet werden: mit WorkManager arbeiten.

<ph type="x-smartling-placeholder">
</ph> Daten mit Netzwerkmonitoren und Warteschlangen lesen
Abbildung 3: Lesewarteschlangen mit Netzwerkmonitoring

Schreibvorgänge

Die empfohlene Methode zum Lesen von Daten in einer Offline-First-App ist die Verwendung von beobachtbaren Typen gehören, sind asynchrone APIs das Äquivalent für Schreib-APIs, z. B. als Anhalten-Funktionen. Dadurch wird der UI-Thread nicht blockiert und Fehler werden vermieden. da Schreibvorgänge in Offline-First-Apps beim Überqueren eines Netzwerks fehlschlagen können. Grenze.

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

Im Snippet oben ist Coroutines die asynchrone API der Wahl als API sperrt.

Schreibstrategien

Beim Schreiben von Daten in Offline-First-Apps gibt es drei Strategien. Welche Sie wählen, hängt von der Art der zu schreibenden Daten und den Anforderungen ab. der App:

Ausschließlich online geschriebene Schreibvorgänge

Versuchen Sie, die Daten über die Netzwerkgrenze hinweg zu schreiben. Wenn der Vorgang erfolgreich war, aktualisieren Sie lokale Datenquelle verwenden. Andernfalls wird eine Ausnahme ausgelöst und es wird dem Aufrufer überlassen, angemessen reagieren.

<ph type="x-smartling-placeholder">
</ph> Nur Online-Schreibvorgänge
Abbildung 4: Nur Online-Schreibvorgänge

Diese Strategie wird häufig für Schreibtransaktionen verwendet, die online in nahezu in Echtzeit. Beispiel: Überweisung. Da Schreibvorgänge fehlschlagen können, um dem Nutzer mitzuteilen, dass der Schreibvorgang fehlgeschlagen ist, oder um überhaupt keine Daten zu schreiben. Einige Strategien, die Sie können folgende Szenarien beinhalten:

  • Wenn eine App Internetzugriff zum Schreiben von Daten benötigt, werden sie möglicherweise nicht angezeigt. eine Benutzeroberfläche für den Nutzer, über die er Daten schreiben kann. wird es deaktiviert.
  • Sie können eine Pop-up-Nachricht, die der Nutzer nicht schließen kann, oder eine vorübergehende Aufforderung verwenden. um den Nutzer zu informieren, dass er offline ist.

Schreibvorgänge in der Warteschlange

Wenn Sie ein Objekt schreiben möchten, fügen Sie es in eine Warteschlange ein. Fortfahren um die Warteschlange mit exponentiellem Backoff zu leeren, sobald die App wieder online ist. An Das Draining einer Offline-Warteschlange ist andauernde Arbeit, WorkManager

<ph type="x-smartling-placeholder">
</ph> Schreibwarteschlangen mit Wiederholungsversuchen
Abbildung 5: Schreibwarteschlangen mit Wiederholungsversuchen

Dieser Ansatz ist in folgenden Fällen eine gute Wahl:

  • Es ist nicht unbedingt notwendig, dass die Daten in das Netzwerk geschrieben werden.
  • Die Transaktion ist nicht zeitkritisch.
  • Es ist nicht unbedingt erforderlich, dass der Nutzer informiert wird, wenn der Vorgang fehlschlägt.

Anwendungsfälle für diesen Ansatz umfassen Analyseereignisse und Logging.

Verzögerte Schreibvorgänge

Zuerst in die lokale Datenquelle schreiben und dann Schreibvorgänge in die Warteschlange stellen, um das Netzwerk zu benachrichtigen sobald wie möglich. Das ist nicht einfach, da es zu Konflikten kommen kann. zwischen dem Netzwerk und den lokalen Datenquellen verbunden ist, sobald die App wieder online ist. Die im nächsten Abschnitt zur Konfliktlösung.

<ph type="x-smartling-placeholder">
</ph> Verzögerte Schreibvorgänge mit Netzwerkmonitoring
Abbildung 6: Verzögerte Schreibvorgänge

Dieser Ansatz ist die richtige Wahl, wenn die Daten für die App entscheidend sind. Für In einer Offline-First-App für To-do-Listen ist es beispielsweise wichtig, Offlineinhalte werden lokal gespeichert, um das Risiko eines Datenverlusts zu vermeiden.

Synchronisierung und Konfliktlösung

Wenn eine Offline-App ihre Verbindung wiederherstellt, muss sie Daten in seiner lokalen Datenquelle mit denen in der Netzwerkdatenquelle. Dieser Prozess wird als Synchronisierung bezeichnet. Es gibt zwei Möglichkeiten, wie eine App synchronisiert werden kann: Netzwerkdatenquelle an:

  • Pull-basierte Synchronisierung
  • Push-basierte Synchronisierung

Pull-basierte Synchronisierung

Bei der Pull-basierten Synchronisierung wendet sich die App an das Netzwerk, um die immer auf dem neuesten Stand. Eine gängige Heuristik für diesen Ansatz ist navigationsbasiert, wobei die App Daten nur kurz abruft, bevor sie sie dem Nutzenden.

Dieser Ansatz funktioniert am besten, wenn die App kurze bis Zwischenphasen erwartet Keine Netzwerkverbindung. Das liegt daran, dass die Datenaktualisierung opportunistisch ist und lange Zeiträume ohne Verbindung erhöhen die Wahrscheinlichkeit, dass der Nutzer versucht, App-Ziele aufrufen, deren Cache veraltet oder leer ist.

<ph type="x-smartling-placeholder">
</ph> Pull-basierte Synchronisierung
Abbildung 7: Pull-basierte Synchronisierung: Gerät A greift auf Ressourcen für die Bildschirme A und B zu während Gerät B nur für die Bildschirme B, C und D auf Ressourcen zugreift

Stellen wir uns eine App vor, bei der Seitentokens zum Abrufen von Elementen in einer Endlosschleife verwendet werden. für einen bestimmten Bildschirm durch. Die Implementierung kontaktiert möglicherweise im Netzwerk, speichern die Daten dauerhaft in der lokalen Datenquelle und lesen sie dann aus dem lokale Datenquelle verwenden, um dem Nutzer Informationen zu präsentieren. Für den Fall, dass keine Netzwerkverbindung vorhanden ist, kann das Repository Daten vom lokalen Datenquelle allein. Dieses Muster wird von der Jetpack-Auslagerungsbibliothek verwendet. mit seiner 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
}

Die Vor- und Nachteile einer Pull-basierten Synchronisierung sind im Folgenden zusammengefasst: in der folgenden Tabelle:

Vorteile Nachteile
Relativ einfach zu implementieren. Anfällig für intensive Datennutzung. Der Grund hierfür ist, dass wiederholte Besuche eines Navigationsziels dazu führen, dass unveränderte Informationen unnötigerweise abgerufen werden. Sie können dies durch ordnungsgemäßes Caching umgehen. Dies kann auf der UI-Ebene mit dem Operator cachedIn oder auf der Netzwerkebene mit einem HTTP-Cache erfolgen.
Nicht benötigte Daten werden niemals abgerufen. Skaliert nicht gut mit relationalen Daten, da das abgerufene Modell eigenständig sein muss. Wenn das zu synchronisierende Modell davon abhängt, dass andere Modelle abgerufen werden, um sich selbst zu füllen, wird das bereits erwähnte Problem aufgrund der hohen Datennutzung sogar noch größer werden. Darüber hinaus kann es zu Abhängigkeiten zwischen den Repositories des übergeordneten Modells und den Repositories des verschachtelten Modells führen.

Push-basierte Synchronisierung

Bei der Push-basierten Synchronisierung versucht die lokale Datenquelle, ein Replikat zu imitieren. Netzwerkdatenquelle optimal nutzen. Proaktiv ruft beim ersten Start eine angemessene Menge an Daten ab, um Es nutzt Benachrichtigungen vom Server, um es zu warnen, wenn diese Daten sind veraltet.

<ph type="x-smartling-placeholder">
</ph> Push-basierte Synchronisierung
Abbildung 8: Push-basierte Synchronisierung: Das Netzwerk benachrichtigt die App, wenn sich Daten ändern und Die App antwortet, indem sie die geänderten Daten abruft.

Nach Erhalt der veralteten Benachrichtigung nimmt die App Kontakt mit dem Netzwerk auf, um nur die Daten aktualisieren, die als veraltet markiert sind. Diese Arbeit wird an die Repository, die die Netzwerkdatenquelle erreicht und die Daten speichert in der lokalen Datenquelle abgerufen werden. Da das Repository seine Daten werden die Leser über alle Änderungen informiert.

class UserDataRepository(...) {

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

Bei diesem Ansatz ist die Anwendung weitaus weniger von der Datenquelle des Netzwerks abhängig über einen längeren Zeitraum ohne es funktionieren. Sie bietet Lese- und Schreibzugriff offline verfügbar, da angenommen wird, dass die aktuellen Informationen aus der Netzwerkdatenquelle.

Die Vor- und Nachteile der Push-basierten Synchronisierung sind zusammengefasst in in der folgenden Tabelle:

Vorteile Nachteile
Die App kann auf unbestimmte Zeit offline bleiben. Die Versionsverwaltung von Daten für die Konfliktlösung ist nicht einfach.
Mindestdatenverbrauch. Die App ruft nur geänderte Daten ab. Sie müssen bei der Synchronisierung Schreibbedenken berücksichtigen.
Funktioniert gut für relationale Daten. Jedes Repository ist nur für das Abrufen von Daten für das unterstützte Modell verantwortlich. Die Netzwerkdatenquelle muss die Synchronisierung unterstützen.

Hybridsynchronisierung

Einige Anwendungen nutzen einen hybriden Ansatz, der Pull- oder Push-Verfahren verwendet, je nachdem, Daten. Eine Social-Media-App kann beispielsweise die Pull-basierte Synchronisierung Den folgenden Feed des Nutzers aufgrund der hohen Feedhäufigkeit on demand abrufen Aktualisierungen. Dieselbe App kann auch die Push-basierte Synchronisierung für Daten über den angemeldeten Nutzer einschließlich Nutzername, Profilbild usw.

Letztendlich hängt die Wahl der Offline-First-Synchronisierung von den Produktanforderungen ab. und der verfügbaren technischen Infrastruktur.

Konfliktlösung

Wenn die App offline Daten schreibt, die nicht mit dem Netzwerk übereinstimmen Datenquelle ist ein Konflikt aufgetreten, den Sie beheben müssen, bevor Synchronisierung stattfinden kann.

Eine Konfliktlösung erfordert häufig eine Versionsverwaltung. Die App muss einige Schritte Buchführung, um den Zeitpunkt von Änderungen nachzuverfolgen. Dadurch kann es den der Netzwerkdatenquelle zu. Die Netzwerkdatenquelle hat dann eine absolute Informationsquelle bereitzustellen. Es gibt ein breites Spektrum der Konfliktlösung zu berücksichtigen, je nach den Bedürfnissen . Bei mobilen Apps lautet ein gängiger Ansatz „Last Write Wins“.

Letzter Schreibvorgang gewinnt

Bei diesem Ansatz hängen Geräte Zeitstempelmetadaten an die Daten an, in die sie schreiben. Netzwerk. Wenn die Netzwerkdatenquelle sie empfängt, werden alle Daten verworfen. die älter als ihr aktueller Zustand sind, und akzeptieren diejenigen, die neuer als der aktuelle Zustand sind.

<ph type="x-smartling-placeholder">
</ph> Letzter Schreibvorgang gewinnt Konfliktlösung
Abbildung 9: "Der letzte Schreibvorgang gewinnt" Die „Source of Truth“ für Daten wird von der letzten Entität bestimmt zum Schreiben von Daten

Im obigen Beispiel sind beide Geräte offline und werden anfangs mit dem Netzwerkdatenquelle. Während sie offline sind, schreiben beide Daten lokal und verfolgen der Zeit, in der sie ihre Daten geschrieben haben. Wenn beide wieder online sind und mit der Netzwerkdatenquelle synchronisieren, behebt das Netzwerk den Konflikt Daten von Gerät B dauerhaft gespeichert, da die Daten später geschrieben wurden.

WorkManager in Offline-Apps

Bei den oben beschriebenen Lese- und Schreibstrategien gibt es zwei gängige Dienstprogramme:

  • Warteschlangen <ph type="x-smartling-placeholder">
      </ph>
    • Lesevorgänge: Wird verwendet, um Lesevorgänge zu verschieben, bis eine Netzwerkverbindung verfügbar ist.
    • Schreibvorgänge: Wird verwendet, um Schreibvorgänge zu verschieben, bis die Netzwerkverbindung hergestellt ist. und Schreibvorgänge für Wiederholungsversuche neu in die Warteschlange zu stellen.
  • Überwachung der Netzwerkkonnektivität <ph type="x-smartling-placeholder">
      </ph>
    • Lesevorgänge: Wird als Signal verwendet, um die Lesewarteschlange zu leeren, wenn die Anwendung verbunden und für die Synchronisierung
    • Schreibvorgänge: Wird als Signal verwendet, um die Schreibwarteschlange zu leeren, wenn die Anwendung verbunden und für die Synchronisierung

Beide Fälle sind Beispiele für harte Arbeit, die WorkManager herausragend macht. Beispiel: In der Beispiel-App Now in Android WorkManager wird bei der Synchronisierung sowohl als Lesewarteschlange als auch als Netzwerkmonitor verwendet. aus der lokalen Datenquelle. Beim Start führt die App die folgenden Aktionen aus:

  1. Die Synchronisierung von Warteschlangenlesevorgängen sorgt für Parität zwischen den lokale Datenquelle und Netzwerk-Datenquelle.
  2. Leeren Sie die Lesesynchronisierungswarteschlange und starten Sie die Synchronisierung, wenn die Anwendung im Internet.
  3. Führt einen Lesevorgang aus der Netzwerkdatenquelle mithilfe des exponentiellen Backoffs durch.
  4. Die Ergebnisse des Lesevorgangs in der lokalen Datenquelle werden beibehalten, auftretenden Konflikten.
  5. Stellen Sie die Daten aus der lokalen Datenquelle für andere Ebenen der Anwendung für verbrauchen.

Dies wird im folgenden Diagramm dargestellt:

<ph type="x-smartling-placeholder">
</ph> Datensynchronisierung in der Now App für Android
Abbildung 10: Datensynchronisierung in der App „Now in Android“

Die Einreihung der Synchronisierung mit WorkManager erfolgt wie folgt: und es als Unique work mit KEEP ExistingWorkPolicy angeben:

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

Dabei ist SyncWorker.startupSyncWork() so definiert:


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

Genauer gesagt erfordern die Constraints, die durch SyncConstraints definiert sind, Folgendes: Die NetworkType sind NetworkType.CONNECTED. Das heißt, es wartet, bis der Netzwerk verfügbar ist, bevor es ausgeführt wird.

Sobald das Netzwerk verfügbar ist, wird die Warteschlange vom Worker per Drain beendet. angegebenen SyncWorkName durch Delegieren an den entsprechenden Repository Instanzen. Wenn die Synchronisierung fehlschlägt, gibt die Methode doWork() Folgendes zurück: Result.retry(). WorkManager versucht automatisch, die Synchronisierung mit exponentiellen Backoffs. Andernfalls werden Result.success() abgeschlossen. Synchronisierung.

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

Produktproben

In den folgenden Google-Beispielen werden Offline-First-Apps veranschaulicht. Sehen Sie sich diese Tipps in der Praxis an: