Communicating between loosely coupled components
Even though elements of solutions should have low coupling between each other, it is common for modules, components or platform specific elements (for example renderer) to communicate with each other. There are several loosely coupled communication patterns, each with their own strengths. Typically, combinations of these patterns are used to create the resulting solution. The following are some of these patterns:
Shared resources. If you do not want elements to directly communicate with each other, you can also have them communicate indirectly through a shared resource, such as a database or a set of web services.
Shared services. A shared service is a class that can be accessed through a common interface. Typically, shared services are found in a shared assembly and provide system-wide services or through DI container.
Loosely coupled events. A element can broadcast that a certain event has occurred. Other elements can subscribe to those events, so they will be notified when the event occurs. Loosely coupled events are a lightweight manner of setting up communication between two elements; therefore, they are easily implemented. However, a design that relies too heavily on events can become hard to maintain, especially if many events must be orchestrated together to fulfill a single task. In that case, it might be better to consider a shared service.
Loosely coupled events
Xamarin.Forms provides an event mechanism that enables communications between loosely coupled components in the application. This mechanism allows publishers and subscribers to communicate through events and still do not have a direct reference to each other.
For more information see Communicating Between Loosely Coupled Components
This means there can be multiple publishers that raise the same event and there can be multiple subscribers listening to the same event
Publish Event
Publishers raise an event by calling the 'Send' method.
Xamarin.Forms.MessagingCenter.Send("",
DEM.Core.Global.MESSAGE_EVENT_APP_LAUNCHED_FROM_DEEP_LINK
, url.ToString());
Subscribing to Events
Subscribers can enlist with on event using the Subscribe method on the 'MessagingCenter' class
if (!IsMessagingCenterSubscribed)
{
MessagingCenter.Subscribe<string, string>("",
DEM.Core.Global.MESSAGE_EVENT_APP_LAUNCHED_FROM_DEEP_LINK, (sender, args) =>
{
App.SetDeppLink(new Uri(args));
});
IsMessagingCenterSubscribed = true;
}
Note
Be sure to subscribe only once with 'Subscribe' method on 'MessagingCenter' class, for example use always Unsubscribe before using Subscribe or with flage 'IsMessagingCenterSubscribed' like in example above.
Use Messages with Background Processes. If you are downloading a file in the background, or performing some other intensive task, completely separate to the app, then MessagingCenter is a good way to transmit messages across two distinctly different operations.
Frequently, subscribers will need to update UI elements in response to events. In Xamarin, only a UI thread can update UI elements.
MessagingCenter.Subscribe<string, string>("",
DEM.Core.Global.MESSAGE_EVENT_APP_LAUNCHED_FROM_DEEP_LINK, (sender, args) =>
{
Xamarin.Forms.Device.BeginInvokeOnMainThread(() =>
{
IsLoadingMore = false;
});
});
Another example of use is for communication between platform specific elements (Andorid or iOS) and other elements in solution, but do not use messaging center for communication between
- ViewModel to Page -> use binding instead
- Page to Page -> states should be managed by the Model, through the ViewMode and binding
- ViewModel to ViewModel -> use navigation instead
- Model to Model or Service -> use shared services instead
Subscribing Using Strong References
If you are raising multiple events in a short period of time and have noticed performance concerns with them, you may need to subscribe with strong delegate references.
App.Session.UserIsLoggedInChange -= Session_UserIsLoggedInChange;
App.Session.UserIsLoggedInChange += Session_UserIsLoggedInChange;
...
protected virtual void Session_UserIsLoggedInChange(object sender, bool args)
{
...
}
If you do that, you will then need to manually unsubscribe from the event when disposing the subscriber.
protected override void UnhookView()
{
base.UnhookView();
App.Session.UserIsLoggedInChange -= Session_UserIsLoggedInChange;
}
For more information about Xamarin.Forms MessagingCenter, see MessagingCenter
For more information about weak references, see Weak References on MSDN.
Shared services
Another method of cross-element communication is through shared services. When the modules are loaded, application and modules add their services to the DI container. Typically, services are registered and retrieved from a application using ContainerProvider by common interface types. This allows modules to use services provided by other modules without requiring a static reference to the module. Service instances are shared across modules, so you can share data and pass messages between elements.
In the our banking sample implementation, the Account Data module provides an implementation
of IAccountService
. The Payment module consumes these services by using the application's
base dependency injection container, which provides service location and resolution.
public interface IAccountService : IApplicationService, IMultiCurrencyAccountService
{
Task<bool> GetData(string accountNumber, DataObject<Account> data = null, Action<DataObject<Account>> onData = null);
Task<bool> GetList(List<string> accList = null, string code = null, Action<RangedObservableCollection<DataObject<Account>>> onData = null);
Task<bool> SelectCurrency(DataObject<Account> data, string currency);
DataObject<Account> GetDataObject(Account account);
}
The IAccountService
is meant to be consumed by other modules, so it can be found in the DEM.Core
common assembly, but the concrete implementation of this interface does not need to be shared, so
it is defined directly in the Account Data module and can be updated independently of other modules.
The Payment module's receives the IAccountService
service through constructor dependency injection.
public StandardPaymentViewModel(IAccountService accountService)
This helps with cross-element communication because service consumers do not need a static reference to elements providing the service. This service can be used to send or receive data between elements.
Note
DEM uses AutoFac containers with explicit registration. In these cases, the registration typically occurs during module loading when DEM invokes the 'IModule.RegisterServices' method. See Modular Application Development for more information.
For more information about platform specific services see Using the DependencyService with DEM