I’m using CBLite 2.6.0, Community Edition 6.0.0 build 1693 and Couchbase Sync Gateway/2.6.0(127;b4c828d) CE.
Developing on Mac using Visual Studio 2019 - and Xamarin.Forms 4.4.0.xxxx (and same issue was on 4.3.0.yyyy)
I have developed an app that syncs with the server - and when the sync. process is running on Android everything is very sluggish or non-responding. Once sync goes idle - everything starts working again. On iOS it just works smoothly…
I guess I must be doing something wrong in the way I start the replication task. But I have built it based on inputs from various examples and questions so I don’t really know what to do to fix this. In my service I have this method that I call on start:
public async Task InitAsync()
{
var options = new DatabaseConfiguration();
// Borrow this functionality from Couchbase Lite
var defaultDirectory = Service.GetInstance<IDefaultDirectoryResolver>().DefaultDirectory();
var dbFolder = Path.Combine(defaultDirectory, "data");
if (!Directory.Exists(dbFolder))
{
Directory.CreateDirectory(dbFolder);
}
options.Directory = dbFolder;
Logger.Debug($"Will open/create DB at path {dbFolder}");
if (!Database.Exists(AppConstants.DB_NAME, dbFolder))
{
// Load prebuilt database to path
await seedService.CopyDatabaseAsync(dbFolder);
Db = new Database(AppConstants.DB_NAME, options);
CreateDatabaseIndexes();
Analytics.TrackEvent("Installed prebuilt database");
//Db = new Database(AppConstants.DB_NAME, options); // TODO: Reinstate prebuilt db once new version attached!
}
else
{
Db = new Database(AppConstants.DB_NAME, options);
}
// Database.SetLogLevel(Couchbase.Lite.Logging.LogDomain.Replicator, Couchbase.Lite.Logging.LogLevel.Debug);
// Listen to events from app about going to sleep or wake up
eventAggregator.GetEvent<OnBackgroundEvent>().Subscribe(HandleReplicator);
eventAggregator.GetEvent<OnConnectivityEvent>().Subscribe(HandleReplicator);
eventAggregator.GetEvent<OnLoginEvent>().Subscribe(RestartReplicator);
eventAggregator.GetEvent<OnImagePushedEvent>().Subscribe(DisableReplication);
AppUtils.LastReplication = null;
StartReplicator();
return;
}
And this is the StartReplicator
method:
void StartReplicator()
{
AppUtils.ConnectionStatus = ReplicatorActivityLevel.Offline;
if (null == Db)
{
Crashes.TrackError(null, new Dictionary<string, string> { { "StartReplicator", "Database not set!" } });
throw new InvalidOperationException("Database not set!");
}
// Anonymous settings
var username = AppUtils.AnonymousUserId;
var password = AppUtils.AnonymousPassword;
//var replType = ReplicatorType.Pull; TODO: Should we revert to PULL?? - Pull-push avoids the crash error right now...
var replType = ReplicatorType.PushAndPull;
var channels = new[] { "!" };
if (AppUtils.IsLoggedIn || AppUtils.ActivationPending)
{
username = AppUtils.UserId;
password = AppUtils.Password;
channels = new[] { "!", $"channel.{username}" };
//replType = ReplicatorType.PushAndPull; TODO: Reinstate if pull only above is reverted to
}
var dbUrl = new Uri(syncUrl, AppConstants.DB_NAME);
Logger.Debug($"Start {replType} Replicator: Will connect to: {syncUrl} - user: '{username}'" + (AppUtils.ActivationPending ? " (activation pending!)" : ""));
var config = new ReplicatorConfiguration(Db, new URLEndpoint(syncUrl))
{
ReplicatorType = replType,
Continuous = true,
Authenticator = new BasicAuthenticator(username, password),
Channels = AppUtils.IsLoggedIn ? new[] { "!", $"channel.{username}" } : new[] { "!" }
};
config.PushFilter = (document, flags) =>
{
if (document.GetBoolean(nameof(BaseDoc.IsSyncDisabled).ToLower()))
{
Logger.Debug($"Pushfilter: Replication diabled for {document.Id}. Skip...");
return false;
}
return true;
};
replicator = new Replicator(config);
replListener = replicator.AddChangeListener((sender, args) =>
{
if (args is null) return;
var s = args.Status;
AppUtils.ConnectionStatus = s.Activity;
Logger.Debug($"{replType} Replicator: {s.Progress.Completed}/{s.Progress.Total}{(string.IsNullOrEmpty(s.Error?.Message) ? "" : $", error {s.Error?.Message}")}, activity = {s.Activity}");
if (!(s.Error is null))
{
if (s.Error is CouchbaseWebsocketException)
{
var cbex = s.Error as CouchbaseWebsocketException;
if (CouchbaseLiteErrorType.CouchbaseLite.Equals(cbex?.Domain) && CouchbaseLiteError.HTTPAuthRequired.Equals(cbex?.Error))
{
// If this is the anonymous user then we have a serious problem....
if (AppUtils.AnonymousUserId.Equals(username))
{
Crashes.TrackError(null, new Dictionary<string, string> { { "StartReplicator", $"User '{username}' with password: '{password}' cannot authenticate with server!!!" } });
Logger.Debug($"User '{username}' with password: '{password}' cannot authenticate with server!!! - Replication will not run");
StopReplicator();
return;
}
else
{
Logger.Debug($"Wrong credentials for user: {username}. Disable auto login...");
AppUtils.LoggedInTime = DateTime.MinValue;
AppUtils.Password = null;
// Retry start of replication without a logged in user - carefull here....!
StartReplicator();
return;
}
}
}
Logger.Debug($"Error :: {s.Error}");
}
else if (s.Activity == ReplicatorActivityLevel.Idle)
{
AppUtils.LastReplication = DateTime.Now;
if (s.Progress.Completed > 0)
{
eventAggregator.GetEvent<OnDataUpdateEvent>().Publish(true);
}
}
eventAggregator.GetEvent<OnSyncStatusEvent>().Publish(s.Activity);
});
docListener = replicator.AddDocumentReplicationListener((sender, args) =>
{
// TODO: Consider checking docs (e.g. ispublic and userkey) to send more specific update events!!
// Remove Image docs after they have been sent to the server. Images should be read from url's
if (args.IsPush)
{
Logger.Debug($"Push replication finished sending {args.Documents.Count} documents");
foreach (var document in args.Documents)
{
if (document.Error == null)
{
Logger.Debug($" Doc ID: {document.Id}" + (document.Flags.HasFlag(DocumentFlags.Deleted) ? " (deletion)" : "") + (document.Flags.HasFlag(DocumentFlags.AccessRemoved) ? " (access removed)" : ""));
if (!document.Flags.HasFlag(DocumentFlags.Deleted))
{
if (typeof(Image).Name.Equals(BaseDoc.GetTypeFromID(document.Id)))
{
eventAggregator.GetEvent<OnImagePushedEvent>().Publish(document.Id);
}
}
}
else
{
Logger.Error($" {(string.IsNullOrEmpty(document.Id) ? "" : $"Doc.id: {document.Id}, ")}Error: {document.Error}");
}
}
}
else
{
// TODO: Probably needs to be "silenced"/removed...
Logger.Debug($"Pull replication finished receiving {args.Documents.Count} documents");
foreach (var document in args.Documents)
{
if (document.Error == null)
{
// Update User's full name in preferences if there is an update from the server.
if (AppUtils.IsLoggedIn && string.Equals(document.Id, BaseDoc.GetID(typeof(User).Name, AppUtils.UserId)))
{
var doc = Db.GetDocument(document.Id);
if (!(doc is null))
{
var user = doc.ToObject<User>();
AppUtils.UserFullName = user.AbbreviatedName;
Logger.Debug($" Updated full name: {user.AbbreviatedName} in preferences for logged in user id: {AppUtils.UserId}");
if (user.Deleted)
{
AppUtils.StayLoggedIn = false;
AppUtils.Password = null;
AppUtils.LoggedInTime = DateTime.MinValue;
eventAggregator.GetEvent<OnLoginEvent>().Publish();
}
}
}
Logger.Debug($" Doc ID: {document.Id}" + (document.Flags.HasFlag(DocumentFlags.Deleted) ? " (deletion)" : "") + (document.Flags.HasFlag(DocumentFlags.AccessRemoved) ? " (access removed)" : ""));
}
else
{
Logger.Error($" {(string.IsNullOrEmpty(document.Id) ? "" : $"Doc.id: {document.Id}, ")}Error: {document.Error}");
}
}
}
});
replicator.Start();
}
Any suggestions or input is very much appreciated as the way it is running now is going to make Android people throw their phones to the ground…