More GA event tracking throughout. Added dispatch when ios app is backgrounded.

This commit is contained in:
Kyle Spearrin 2016-08-04 19:35:56 -04:00
parent dd633d4fc1
commit 7c29f8b77a
13 changed files with 106 additions and 14 deletions

View File

@ -87,9 +87,10 @@ namespace Bit.Android.Services
} }
} }
public void Dispatch() public void Dispatch(Action completionHandler = null)
{ {
_instance.DispatchLocalHits(); _instance.DispatchLocalHits();
completionHandler?.Invoke();
} }
} }
} }

View File

@ -1,4 +1,6 @@
namespace Bit.App.Abstractions using System;
namespace Bit.App.Abstractions
{ {
public interface IGoogleAnalyticsService public interface IGoogleAnalyticsService
{ {
@ -8,6 +10,6 @@
void TrackExtensionEvent(string eventName, string label = null); void TrackExtensionEvent(string eventName, string label = null);
void TrackEvent(string category, string eventName, string label = null); void TrackEvent(string category, string eventName, string label = null);
void TrackException(string message, bool fatal); void TrackException(string message, bool fatal);
void Dispatch(); void Dispatch(Action completionHandler = null);
} }
} }

View File

@ -16,12 +16,14 @@ namespace Bit.App.Pages
private readonly IFolderService _folderService; private readonly IFolderService _folderService;
private readonly IUserDialogs _userDialogs; private readonly IUserDialogs _userDialogs;
private readonly IConnectivity _connectivity; private readonly IConnectivity _connectivity;
private readonly IGoogleAnalyticsService _googleAnalyticsService;
public SettingsAddFolderPage() public SettingsAddFolderPage()
{ {
_folderService = Resolver.Resolve<IFolderService>(); _folderService = Resolver.Resolve<IFolderService>();
_userDialogs = Resolver.Resolve<IUserDialogs>(); _userDialogs = Resolver.Resolve<IUserDialogs>();
_connectivity = Resolver.Resolve<IConnectivity>(); _connectivity = Resolver.Resolve<IConnectivity>();
_googleAnalyticsService = Resolver.Resolve<IGoogleAnalyticsService>();
Init(); Init();
} }
@ -79,6 +81,7 @@ namespace Bit.App.Pages
{ {
await Navigation.PopModalAsync(); await Navigation.PopModalAsync();
_userDialogs.Toast("New folder created."); _userDialogs.Toast("New folder created.");
_googleAnalyticsService.TrackAppEvent("CreatedFolder");
} }
else if(saveTask.Result.Errors.Count() > 0) else if(saveTask.Result.Errors.Count() > 0)
{ {

View File

@ -16,6 +16,7 @@ namespace Bit.App.Pages
private readonly IFolderService _folderService; private readonly IFolderService _folderService;
private readonly IUserDialogs _userDialogs; private readonly IUserDialogs _userDialogs;
private readonly IConnectivity _connectivity; private readonly IConnectivity _connectivity;
private readonly IGoogleAnalyticsService _googleAnalyticsService;
public SettingsEditFolderPage(string folderId) public SettingsEditFolderPage(string folderId)
{ {
@ -23,6 +24,7 @@ namespace Bit.App.Pages
_folderService = Resolver.Resolve<IFolderService>(); _folderService = Resolver.Resolve<IFolderService>();
_userDialogs = Resolver.Resolve<IUserDialogs>(); _userDialogs = Resolver.Resolve<IUserDialogs>();
_connectivity = Resolver.Resolve<IConnectivity>(); _connectivity = Resolver.Resolve<IConnectivity>();
_googleAnalyticsService = Resolver.Resolve<IGoogleAnalyticsService>();
Init(); Init();
} }
@ -93,6 +95,7 @@ namespace Bit.App.Pages
{ {
await Navigation.PopModalAsync(); await Navigation.PopModalAsync();
_userDialogs.Toast("Folder updated."); _userDialogs.Toast("Folder updated.");
_googleAnalyticsService.TrackAppEvent("EditedFolder");
} }
else if(saveTask.Result.Errors.Count() > 0) else if(saveTask.Result.Errors.Count() > 0)
{ {

View File

@ -1,13 +1,19 @@
using System; using System;
using Bit.App.Controls; using Bit.App.Controls;
using Xamarin.Forms; using Xamarin.Forms;
using Bit.App.Abstractions;
using XLabs.Ioc;
namespace Bit.App.Pages namespace Bit.App.Pages
{ {
public class SettingsHelpPage : ExtendedContentPage public class SettingsHelpPage : ExtendedContentPage
{ {
private readonly IGoogleAnalyticsService _googleAnalyticsService;
public SettingsHelpPage() public SettingsHelpPage()
{ {
_googleAnalyticsService = Resolver.Resolve<IGoogleAnalyticsService>();
Init(); Init();
} }
@ -103,16 +109,19 @@ namespace Bit.App.Pages
private void EmailCell_Tapped(object sender, EventArgs e) private void EmailCell_Tapped(object sender, EventArgs e)
{ {
_googleAnalyticsService.TrackAppEvent("HelpEmail");
Device.OpenUri(new Uri("mailto:hello@bitwarden.com")); Device.OpenUri(new Uri("mailto:hello@bitwarden.com"));
} }
private void WebsiteCell_Tapped(object sender, EventArgs e) private void WebsiteCell_Tapped(object sender, EventArgs e)
{ {
_googleAnalyticsService.TrackAppEvent("HelpWebsite");
Device.OpenUri(new Uri("https://bitwarden.com")); Device.OpenUri(new Uri("https://bitwarden.com"));
} }
private void BugCell_Tapped(object sender, EventArgs e) private void BugCell_Tapped(object sender, EventArgs e)
{ {
_googleAnalyticsService.TrackAppEvent("HelpBug");
Device.OpenUri(new Uri("https://github.com/bitwarden/mobile")); Device.OpenUri(new Uri("https://github.com/bitwarden/mobile"));
} }

View File

@ -17,6 +17,7 @@ namespace Bit.App.Pages
private readonly IUserDialogs _userDialogs; private readonly IUserDialogs _userDialogs;
private readonly IConnectivity _connectivity; private readonly IConnectivity _connectivity;
private readonly ISettings _settings; private readonly ISettings _settings;
private readonly IGoogleAnalyticsService _googleAnalyticsService;
public SettingsSyncPage() public SettingsSyncPage()
{ {
@ -24,6 +25,7 @@ namespace Bit.App.Pages
_userDialogs = Resolver.Resolve<IUserDialogs>(); _userDialogs = Resolver.Resolve<IUserDialogs>();
_connectivity = Resolver.Resolve<IConnectivity>(); _connectivity = Resolver.Resolve<IConnectivity>();
_settings = Resolver.Resolve<ISettings>(); _settings = Resolver.Resolve<ISettings>();
_googleAnalyticsService = Resolver.Resolve<IGoogleAnalyticsService>();
Init(); Init();
} }
@ -88,6 +90,7 @@ namespace Bit.App.Pages
if(succeeded) if(succeeded)
{ {
_userDialogs.Toast("Syncing complete."); _userDialogs.Toast("Syncing complete.");
_googleAnalyticsService.TrackAppEvent("Synced");
} }
else else
{ {

View File

@ -6,6 +6,7 @@ using Bit.App.Models.Page;
using Plugin.Settings.Abstractions; using Plugin.Settings.Abstractions;
using Xamarin.Forms; using Xamarin.Forms;
using XLabs.Ioc; using XLabs.Ioc;
using Bit.App.Abstractions;
namespace Bit.App.Pages namespace Bit.App.Pages
{ {
@ -13,11 +14,13 @@ namespace Bit.App.Pages
{ {
private readonly IUserDialogs _userDialogs; private readonly IUserDialogs _userDialogs;
private readonly ISettings _settings; private readonly ISettings _settings;
private readonly IGoogleAnalyticsService _googleAnalyticsService;
public ToolsExtensionPage() public ToolsExtensionPage()
{ {
_userDialogs = Resolver.Resolve<IUserDialogs>(); _userDialogs = Resolver.Resolve<IUserDialogs>();
_settings = Resolver.Resolve<ISettings>(); _settings = Resolver.Resolve<ISettings>();
_googleAnalyticsService = Resolver.Resolve<IGoogleAnalyticsService>();
Model = new AppExtensionPageModel(_settings); Model = new AppExtensionPageModel(_settings);
Init(); Init();
@ -59,7 +62,7 @@ namespace Bit.App.Pages
var notStartedButton = new Button var notStartedButton = new Button
{ {
Text = "Enable App Extension", Text = "Enable App Extension",
Command = new Command(() => ShowExtension()), Command = new Command(() => ShowExtension("NotStartedEnable")),
VerticalOptions = LayoutOptions.End, VerticalOptions = LayoutOptions.End,
HorizontalOptions = LayoutOptions.Fill, HorizontalOptions = LayoutOptions.Fill,
Style = (Style)Application.Current.Resources["btn-primary"] Style = (Style)Application.Current.Resources["btn-primary"]
@ -108,7 +111,7 @@ namespace Bit.App.Pages
var notActivatedButton = new Button var notActivatedButton = new Button
{ {
Text = "Enable App Extension", Text = "Enable App Extension",
Command = new Command(() => ShowExtension()), Command = new Command(() => ShowExtension("NotActivatedEnable")),
VerticalOptions = LayoutOptions.End, VerticalOptions = LayoutOptions.End,
HorizontalOptions = LayoutOptions.Fill, HorizontalOptions = LayoutOptions.Fill,
Style = (Style)Application.Current.Resources["btn-primary"] Style = (Style)Application.Current.Resources["btn-primary"]
@ -167,7 +170,7 @@ namespace Bit.App.Pages
var activatedButtonReenable = new Button var activatedButtonReenable = new Button
{ {
Text = "Re-enable App Extension", Text = "Re-enable App Extension",
Command = new Command(() => ShowExtension()), Command = new Command(() => ShowExtension("Re-enable")),
VerticalOptions = LayoutOptions.End, VerticalOptions = LayoutOptions.End,
HorizontalOptions = LayoutOptions.Fill, HorizontalOptions = LayoutOptions.Fill,
Style = (Style)Application.Current.Resources["btn-primaryAccent"] Style = (Style)Application.Current.Resources["btn-primaryAccent"]
@ -200,13 +203,15 @@ namespace Bit.App.Pages
BindingContext = Model; BindingContext = Model;
} }
private void ShowExtension() private void ShowExtension(string type)
{ {
_googleAnalyticsService.TrackAppEvent("ShowExtension", type);
MessagingCenter.Send(Application.Current, "ShowAppExtension", this); MessagingCenter.Send(Application.Current, "ShowAppExtension", this);
} }
public void EnabledExtension(bool enabled) public void EnabledExtension(bool enabled)
{ {
_googleAnalyticsService.TrackAppEvent("EnabledExtension", enabled.ToString());
Model.Started = true; Model.Started = true;
if(!Model.Activated && enabled) if(!Model.Activated && enabled)
{ {

View File

@ -18,6 +18,7 @@ namespace Bit.App.Pages
private readonly IFolderService _folderService; private readonly IFolderService _folderService;
private readonly IUserDialogs _userDialogs; private readonly IUserDialogs _userDialogs;
private readonly IConnectivity _connectivity; private readonly IConnectivity _connectivity;
private readonly IGoogleAnalyticsService _googleAnalyticsService;
public VaultAddSitePage() public VaultAddSitePage()
{ {
@ -25,6 +26,7 @@ namespace Bit.App.Pages
_folderService = Resolver.Resolve<IFolderService>(); _folderService = Resolver.Resolve<IFolderService>();
_userDialogs = Resolver.Resolve<IUserDialogs>(); _userDialogs = Resolver.Resolve<IUserDialogs>();
_connectivity = Resolver.Resolve<IConnectivity>(); _connectivity = Resolver.Resolve<IConnectivity>();
_googleAnalyticsService = Resolver.Resolve<IGoogleAnalyticsService>();
Init(); Init();
} }
@ -139,6 +141,7 @@ namespace Bit.App.Pages
{ {
await Navigation.PopModalAsync(); await Navigation.PopModalAsync();
_userDialogs.Toast("New site created."); _userDialogs.Toast("New site created.");
_googleAnalyticsService.TrackAppEvent("CreatedSite");
} }
else if(saveTask.Result.Errors.Count() > 0) else if(saveTask.Result.Errors.Count() > 0)
{ {

View File

@ -18,6 +18,7 @@ namespace Bit.App.Pages
private readonly IFolderService _folderService; private readonly IFolderService _folderService;
private readonly IUserDialogs _userDialogs; private readonly IUserDialogs _userDialogs;
private readonly IConnectivity _connectivity; private readonly IConnectivity _connectivity;
private readonly IGoogleAnalyticsService _googleAnalyticsService;
public VaultEditSitePage(string siteId) public VaultEditSitePage(string siteId)
{ {
@ -26,6 +27,7 @@ namespace Bit.App.Pages
_folderService = Resolver.Resolve<IFolderService>(); _folderService = Resolver.Resolve<IFolderService>();
_userDialogs = Resolver.Resolve<IUserDialogs>(); _userDialogs = Resolver.Resolve<IUserDialogs>();
_connectivity = Resolver.Resolve<IConnectivity>(); _connectivity = Resolver.Resolve<IConnectivity>();
_googleAnalyticsService = Resolver.Resolve<IGoogleAnalyticsService>();
Init(); Init();
} }
@ -174,6 +176,7 @@ namespace Bit.App.Pages
{ {
await Navigation.PopModalAsync(); await Navigation.PopModalAsync();
_userDialogs.Toast("Site updated."); _userDialogs.Toast("Site updated.");
_googleAnalyticsService.TrackAppEvent("EditedSite");
} }
else if(saveTask.Result.Errors.Count() > 0) else if(saveTask.Result.Errors.Count() > 0)
{ {
@ -241,6 +244,7 @@ namespace Bit.App.Pages
{ {
await Navigation.PopModalAsync(); await Navigation.PopModalAsync();
_userDialogs.Toast("Site deleted."); _userDialogs.Toast("Site deleted.");
_googleAnalyticsService.TrackAppEvent("DeletedSite");
} }
else if((await deleteTask).Errors.Count() > 0) else if((await deleteTask).Errors.Count() > 0)
{ {

View File

@ -62,9 +62,12 @@ namespace Bit.iOS.Core.Services
_tracker.Send(dict); _tracker.Send(dict);
} }
public void Dispatch() public void Dispatch(Action completionHandler = null)
{ {
Gai.SharedInstance.Dispatch(); Gai.SharedInstance.Dispatch((result) =>
{
completionHandler?.Invoke();
});
} }
private void SetUserId() private void SetUserId()

View File

@ -228,7 +228,6 @@ namespace Bit.iOS.Extension
Constants.AppExtensionOldPasswordKey, password); Constants.AppExtensionOldPasswordKey, password);
} }
_googleAnalyticsService.TrackExtensionEvent("AutoFilled", _context.ProviderType);
CompleteRequest(itemData); CompleteRequest(itemData);
} }
@ -240,7 +239,21 @@ namespace Bit.iOS.Extension
var resultsItem = new NSExtensionItem { Attachments = new NSItemProvider[] { resultsProvider } }; var resultsItem = new NSExtensionItem { Attachments = new NSItemProvider[] { resultsProvider } };
var returningItems = new NSExtensionItem[] { resultsItem }; var returningItems = new NSExtensionItem[] { resultsItem };
if(itemData != null)
{
_googleAnalyticsService.TrackExtensionEvent("AutoFilled", _context.ProviderType);
}
else
{
_googleAnalyticsService.TrackExtensionEvent("Closed", _context.ProviderType);
}
_googleAnalyticsService.Dispatch(() =>
{
NSRunLoop.Main.BeginInvokeOnMainThread(() => {
ExtensionContext.CompleteRequest(returningItems, null); ExtensionContext.CompleteRequest(returningItems, null);
});
});
} }
private void SetIoc() private void SetIoc()
@ -296,7 +309,7 @@ namespace Bit.iOS.Extension
var dict = list as NSDictionary; var dict = list as NSDictionary;
action(dict); action(dict);
_googleAnalyticsService.TrackExtensionEvent("ProviderType", type); _googleAnalyticsService.TrackExtensionEvent("ProcessItemProvider", type);
Debug.WriteLine("BW LOG, ProviderType: " + _context.ProviderType); Debug.WriteLine("BW LOG, ProviderType: " + _context.ProviderType);
Debug.WriteLine("BW LOG, Url: " + _context.Url); Debug.WriteLine("BW LOG, Url: " + _context.Url);

View File

@ -166,7 +166,7 @@ namespace Bit.iOS.Extension
if(saveTask.Result.Succeeded) if(saveTask.Result.Succeeded)
{ {
_googleAnalyticsService.TrackExtensionEvent("SiteCreated"); _googleAnalyticsService.TrackExtensionEvent("CreatedSite");
if(SiteListController != null) if(SiteListController != null)
{ {
SiteListController.DismissModal(); SiteListController.DismissModal();

View File

@ -26,12 +26,15 @@ using Bit.App.Pages;
using PushNotification.Plugin.Abstractions; using PushNotification.Plugin.Abstractions;
using HockeyApp.iOS; using HockeyApp.iOS;
using Bit.iOS.Core; using Bit.iOS.Core;
using Google.Analytics;
namespace Bit.iOS namespace Bit.iOS
{ {
[Register("AppDelegate")] [Register("AppDelegate")]
public partial class AppDelegate : global::Xamarin.Forms.Platform.iOS.FormsApplicationDelegate public partial class AppDelegate : global::Xamarin.Forms.Platform.iOS.FormsApplicationDelegate
{ {
private GaiCompletionHandler _dispatchHandler = null;
public ISettings Settings { get; set; } public ISettings Settings { get; set; }
public override bool FinishedLaunching(UIApplication app, NSDictionary options) public override bool FinishedLaunching(UIApplication app, NSDictionary options)
@ -138,8 +141,10 @@ namespace Bit.iOS
UIApplication.SharedApplication.SetStatusBarHidden(true, false); UIApplication.SharedApplication.SetStatusBarHidden(true, false);
// Log the date/time we last backgrounded // Log the date/time we last backgrounded
Settings.AddOrUpdateValue(App.Constants.SettingLastBackgroundedDate, DateTime.UtcNow);
Settings.AddOrUpdateValue(Bit.App.Constants.SettingLastBackgroundedDate, DateTime.UtcNow); // Dispatch Google Analytics
SendGoogleAnalyticsHitsInBackground();
base.DidEnterBackground(uiApplication); base.DidEnterBackground(uiApplication);
Debug.WriteLine("DidEnterBackground"); Debug.WriteLine("DidEnterBackground");
@ -176,6 +181,11 @@ namespace Bit.iOS
public override void WillEnterForeground(UIApplication uiApplication) public override void WillEnterForeground(UIApplication uiApplication)
{ {
SendResumedMessage(); SendResumedMessage();
// Restores the dispatch interval because dispatchWithCompletionHandler
// has disabled automatic dispatching.
Gai.SharedInstance.DispatchInterval = 10;
base.WillEnterForeground(uiApplication); base.WillEnterForeground(uiApplication);
Debug.WriteLine("WillEnterForeground"); Debug.WriteLine("WillEnterForeground");
} }
@ -273,5 +283,38 @@ namespace Bit.iOS
Resolver.SetResolver(new UnityResolver(container)); Resolver.SetResolver(new UnityResolver(container));
} }
/// <summary>
/// This method sends any queued hits when the app enters the background.
/// ref: https://developers.google.com/analytics/devguides/collection/ios/v3/dispatch
/// </summary>
private void SendGoogleAnalyticsHitsInBackground()
{
var taskExpired = false;
var taskId = UIApplication.SharedApplication.BeginBackgroundTask(() =>
{
taskExpired = true;
});
if(taskId == UIApplication.BackgroundTaskInvalid)
{
return;
}
_dispatchHandler = (result) =>
{
// Send hits until no hits are left, a dispatch error occurs, or the background task expires.
if(_dispatchHandler != null && result == DispatchResult.Good && !taskExpired)
{
Gai.SharedInstance.Dispatch(_dispatchHandler);
}
else
{
UIApplication.SharedApplication.EndBackgroundTask(taskId);
}
};
Gai.SharedInstance.Dispatch(_dispatchHandler);
}
} }
} }