Package 

Class ReplicatorWorker


  • 
    public final class ReplicatorWorker
    extends CoroutineWorker
                        

    Implementation Notes:

    Android does not support daemon processes. Although it is less likely to happen on modern phones with lots of memory, Android will still kill off a running application if it needs the memory space to run a new app. Under these circumstance, CouchbaseLite continuous replication makes sense only while an application is in the foreground. Once an application is put in the background, it will, eventually, get killed and replicators will be stopped with prejudice. They will not be restarted until a user manually restarts the app and the replication.

    In addition to this issue, continuous replication is incredibly wasteful of battery. It will force a mobile device to keep its radio on: the second most expensive thing a device can do, battery-wise. Android provides a facility managing long running processes: the WorkManager. Jobs scheduled with the WorkManager are persistent. They are also batched across applications in order to optimize radio use. This package integrates replication into the WorkManage. It works like this: Client code schedules replication work using normal WorkManager code, like this:

    val factory = MyReplicatorFactory().
    WorkManager.getInstance(context).enqueue(
       factory.periodicWorkRequestBuilder(15L, TimeUnit.MINUTES)
            .setBackoffCriteria(BackoffPolicy.EXPONENTIAL, 1L, TimeUnit.HOURS)
            .setConstraints(
                Constraints.Builder()
                    .setRequiresBatteryNotLow(true)
                    .setRequiredNetworkType(NetworkType.CONNECTED)
                    .setRequiresStorageNotLow(true)
                    .build()
            )
            .build())

    The only novelty here is that the client code obtains an instance of a WorkRequest.Builder using one of the factory methods periodicWorkRequestBuilder() or oneShotWorkRequestBuilder() defined on a client-created subclass of WorkManagerReplicatorFactory, MyReplicatorFactory in the code above. These methods the are analogs of the standard PeriodicWorkRequestBuilder() and OneShotWorkRequestBuilder() extension functions and return the appropriate WorkRequest.Builder that can be configured, built and scheduled, as any other WorkManager request.

    Remember, now, that the work manager may start an entirely new instance of the client application, every time it runs a new replication! None of the state from any previous instance of the application -- fields, objects, any of it -- may be available. The ReplicatorWorker, on the other hand, needs a properly constructed ReplicatorConfiguration in order to create and run a new replicator. This is why the client code creates a subclass of WorkManagerReplicatorFactory. When the factory method from the subclass of WorkManagerReplicatorFactory (periodicWorkRequestBuilder() or oneShotWorkRequestBuilder()) supplied the WorkRequest.Builder, it used the value of the factory defined property tag, to store the FQN of the factory class, in the work request. The ReplicatorWorker recovers this class name and creates a new instance using reflection. The instance is responsible for creating a properly constructed WorkManagerReplicatorConfiguration from scratch and supplying it to the ReplicatorWorker.

    A WorkManagerReplicatorConfiguration is very similar to a ReplicatorConfiguration, except that it hides properties that should be configured in the WorkManager request: continuous, heartbeat, maxAttempts, and maxAttemptWaitTime.

    An application can observe the status of a work manager replication by subscribing to its LiveData. Assuming that replications processes are 1-1 with the factory defined tag (an assumption pervasive in this implementation) the client code can obtain a LiveData object that provides ReplicatorStatus updates for the replicator like this:

    val replId = MyReplicatorFactory().tag
    val liveData = WorkManager.getInstance(context)
        .getWorkInfosByTagLiveData(replId)
        .map {
            if (it.isEmpty()) {
                null
            } else {
                it[0].progress.toReplicatorStatus(replId)
            }
        }
    return liveData

    The application terminates a repeating WorkManager replication, normally, by cancelling all of the jobs for the identifying tag:

    WorkManager.getInstance(context).cancelAllWorkByTag(factory.tag)
    • Field Summary

      Fields 
      Modifier and Type Field Description
      private final CoroutineDispatcher coroutineContext
    • Method Summary

      Modifier and Type Method Description
      CoroutineDispatcher getCoroutineContext()
      ListenableWorker.Result doWork()
      • Methods inherited from class androidx.work.CoroutineWorker

        getApplicationContext, getBackgroundExecutor, getId, getInputData, getNetwork, getRunAttemptCount, getTags, getTaskExecutor, getTriggeredContentAuthorities, getTriggeredContentUris, getWorkerFactory, isRunInForeground, isStopped, isUsed, setForegroundAsync, setProgressAsync, setRunInForeground, setUsed, stop
      • Methods inherited from class com.couchbase.lite.internal.ReplicatorWorker

        getForegroundInfo, getForegroundInfoAsync, onStopped, setForeground, setProgress, startWork
      • Methods inherited from class java.lang.Object

        clone, equals, finalize, getClass, hashCode, notify, notifyAll, toString, wait, wait, wait