简体   繁体   中英

MVVMCross Messenger - Subscribing to messages in static class

I am just looking into implementing an MVVMCross Messenger solution that will enable me to upload information to Google Analytics when published from either the iOS application or the PCL.

The problem I have the is that the subscription delgates are not fired after I publish. Can you subscribe to MVVMCross Messenger subscriptions from a static class?

Subscriptions in static class

public static class GoogleAnalyticsWrapper //: IDisposable
{
    private const string TrackingId = "xxxxxxxxxxx";

    private static readonly IMvxMessenger messenger;
    private static readonly MvxSubscriptionToken screenNameToken;
    private static  readonly MvxSubscriptionToken eventToken;
    private static  readonly MvxSubscriptionToken exceptionToken;
    private static  readonly MvxSubscriptionToken performanceToken;
    private static  readonly MvxSubscriptionToken publishToken;
    private static  bool disposed = false;
    private static  SafeHandle handle;

    static GoogleAnalyticsWrapper()
    {
        Gai.SharedInstance.DispatchInterval = 60;
        Gai.SharedInstance.TrackUncaughtExceptions = true;

        Gai.SharedInstance.GetTracker(TrackingId); 

        messenger = new MvxMessengerHub();// Mvx.Resolve<IMvxMessenger>();
        screenNameToken = messenger.Subscribe<GaScreenNameMessage>((m) => SetScreenName(m));

        int count = messenger.CountSubscriptionsFor<GaScreenNameMessage>();

        eventToken = messenger.Subscribe<GaEventMessage>(CreateEvent);
        exceptionToken = messenger.Subscribe<GaExceptionMessage>(CreateException);
        performanceToken = messenger.Subscribe<GaPerformanceTimingMessage>(CreatePerformanceMetric);
        publishToken = messenger.Subscribe<GaPublishMessage>(PublishAll);
    }

    public static string Dummy { get; set; }

    public static void SetScreenName(GaScreenNameMessage message) 
    {
        System.Diagnostics.Debugger.Break();
        Gai.SharedInstance.DefaultTracker.Set(GaiConstants.ScreenName, message.ScreenName);
        Gai.SharedInstance.DefaultTracker.Send(DictionaryBuilder.CreateScreenView().Build());
    }

    public static  void CreateEvent(GaEventMessage message) 
        => Gai.SharedInstance.DefaultTracker.Send(DictionaryBuilder.CreateEvent(message.Category, message.Action, message.Label, message.Number).Build());

    private static  void CreateException(GaExceptionMessage message) 
        => Gai.SharedInstance.DefaultTracker.Send(DictionaryBuilder.CreateException(message.ExceptionMessage, message.IsFatal).Build());

    private static  void CreatePerformanceMetric(GaPerformanceTimingMessage message)
        => Gai.SharedInstance.DefaultTracker.Send(DictionaryBuilder.CreateTiming(message.Category, message.Milliseconds, message.Name, message.Label).Build());

    private static  void PublishAll(GaPublishMessage message) 
        => Gai.SharedInstance.Dispatch();

    public static  void Dispose()
    {
        Dispose(true);
        GC.SuppressFinalize(this);
    }

    private void Dispose(bool disposing)
    {
        if (!disposed)
        {
            if (disposing)
            {
                // Dispose managed resources.
                if (handle != null)
                {
                    handle.Dispose();
                }
            }

            // Dispose unmanaged managed resources.
            disposed = true;
        }
    }
}

Publication

messengerService.Publish<GaEventMessage>(new GaEventMessage(this, "Event", "Publish Event", "Publish Event From First View Model", 123));

The problem is, that you are creating a new MvxMessengerHub in your static class, but ( I guess ) inject IMvxMessenger in your consuming classes, which is created by MvvMCross during the initialization lifecycle and so a different instance.

The easy solution would be to initialize it in your App.cs like

public class App : Cirrious.MvvmCross.ViewModels.MvxApplication
{
    public override void Initialize()
    {
        // ...

        var m = Cirrious.CrossCore.Mvx.Resolve<IMvxMessenger>();
        GoogleAnalyticsWrapper.Initialize(m); 
        // ...
    }
}

With a wrapper like this

public static class GoogleAnalyticsWrapper
{
    static void Initialize(IMvxMessenger messenger)
    {
        Gai.SharedInstance.DispatchInterval = 60;
        Gai.SharedInstance.TrackUncaughtExceptions = true;
        Gai.SharedInstance.GetTracker(TrackingId); 

        screenNameToken = messenger.Subscribe<GaScreenNameMessage>((m) => SetScreenName(m));

        int count = messenger.CountSubscriptionsFor<GaScreenNameMessage>();

        eventToken = messenger.Subscribe<GaEventMessage>(CreateEvent);
        exceptionToken = messenger.Subscribe<GaExceptionMessage>(CreateException);
        performanceToken = messenger.Subscribe<GaPerformanceTimingMessage>(CreatePerformanceMetric);
        publishToken = messenger.Subscribe<GaPublishMessage>(PublishAll);
    }

    // ...
}   

Advanced Hint

But as far as I see, you don't even need messaging for this case, because it's one to one "communication". I think it would be nice, if you move the functionality of your GoogleAnalyticsWrapper into a well defined Service like:

interface ITrackingService
{  
    void SetScreenName(GaScreenNameMessage message);
    void CreateEvent(GaEventMessage message);
    void CreateException(GaExceptionMessage message);
    void CreatePerformanceMetric(GaPerformanceTimingMessage message);
    void PublishAll(GaPublishMessage message);
}

public class GoogleAnalyticsTrackingService : ITrackingService
{
    private const string TrackingId = "xxxxxxxxxxx";

    public GoogleAnalyticsTrackingService()
    {
        Gai.SharedInstance.DispatchInterval = 60;
        Gai.SharedInstance.TrackUncaughtExceptions = true;

        Gai.SharedInstance.GetTracker(TrackingId);
    }

    public void SetScreenName(GaScreenNameMessage message) 
    {
        Gai.SharedInstance.DefaultTracker.Set(GaiConstants.ScreenName, message.ScreenName);
        Gai.SharedInstance.DefaultTracker.Send(DictionaryBuilder.CreateScreenView().Build());
    }

    public void CreateEvent(GaEventMessage message) 
    {
        Gai.SharedInstance.DefaultTracker.Send(DictionaryBuilder.CreateEvent(message.Category, message.Action, message.Label, message.Number).Build());
    }   

    private void CreateException(GaExceptionMessage message) 
    {
        Gai.SharedInstance.DefaultTracker.Send(DictionaryBuilder.CreateException(message.ExceptionMessage, message.IsFatal).Build());
    }

    private void CreatePerformanceMetric(GaPerformanceTimingMessage message)
    {
        Gai.SharedInstance.DefaultTracker.Send(DictionaryBuilder.CreateTiming(message.Category, message.Milliseconds, message.Name, message.Label).Build());
    }

    private void PublishAll(GaPublishMessage message) 
    {
        Gai.SharedInstance.Dispatch();
    }
}

That has to be registered in your App

Mvx.LazyConstructAndRegisterSingleton<ITrackingService, GoogleAnalyticsTrackingService>();

And can be consumed with constructor injection or manual resolves

class MyViewModel : MvxViewModel 
{
    public MyViewModel(ITrackingService tracking)
    {
         tracking.CreateEvent(new GaEventMessage(this, "Event", "Publish Event", "Publish Event From First View Model", 123));
    }
}

// or
class MyViewModel : MvxViewModel 
{
    public MyViewModel()
    {
         var tracking = Mvx.Resolve<ITrackingService>();
         tracking.CreateEvent(new GaEventMessage(this, "Event", "Publish Event", "Publish Event From First View Model", 123));
    }
}

There is still one Problem: The interface has still a dependency to google analytics. But the dependency can be easily removed by using multiple parameters instead of a parameter object.

interface ITrackingService
{
    void CreateEvent(string eventName, string title, string message, params object[] additionalParams);
    // ...
} 

// call:
tracking.CreateEvent("Event", "Publish Event", "Publish Event From First View Model", 123);

With this, you are able to unit test it and exchange the tracking service with litte effort, if your stakeholders decide to switch to adobe omniture or whatever.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM