Home > Blockchain >  Xamarin, WPF, UWP solution architecture MVVM (project referencing)
Xamarin, WPF, UWP solution architecture MVVM (project referencing)

Time:05-30

I have been working on Xamarin Forms application (this question suits WPF and UWP as well) and came across a problem that I think can be have the roots in my current project architecture.

Here is an example of application similar to my current one:

Solution MyProject

 MyProject.GeoTracker
   GeoLocationProvider.cs
 MyProject
   MainViewModel.cs
   SettingsViewModel.cs
 MyProject.Android
 MyProject.iOS
 MyProject.Shared

MyProject.GeoTracker is referenced in MyProject, so that MyProject has reference of MyProject.GeoTracker.

In SettingsViewModel.cs I have an EventHandler:

public event EventHandler<EventArgs> SettingsChanged;
protected virtual void OnSettingsChanged() => this.SettingsChanged?.Invoke(this, new EventArgs()); 

Then in MainViewModel.cs:

public MainViewModel(
  ISettingsViewModel settingsViewModel)
{
  this.SettingsViewModel = settingsViewModel;
  this.SettingsViewModel.SettingsChanged  = this.OnSettingsChanged;

  this.GeoLocationProvider = new GeoLocationProvider();
  this.GeoLocationProvider.StartGeoTracking();
  this.GeoLocationProvider.GeoLocationChanged  = this.OnGeoLocationChanged;
} 

This way I am able to track Settings changes in my MainViewModel.cs.

Question: what if I want to do the same in GeoLocationProvider.cs? It is not possible to have circle reference in solution, so I am not able to reference MyProject in MyProject.GeoTracker. What is possible solution?

CodePudding user response:

td;dr A circular reference can be avoided with an appropriate interface, an event handler, or any Dependency Injection scheme.


INTERFACE

The principle is that the place that "needs the work done" does NOT need to know what class performs the work. It just needs a way to say what it wants done.

I'll show an interface, rather than pick some DI library. A DI library makes details cleaner than what I do here.

  1. Define an interface in MyProject.GeoTracker:
public interface ISomeInterface
{
    void DoSomething(object obj);
}
  1. Declare a static variable to hold an implementation of that interface:
public class MyClass1
{
    public ISomeInterface SomeInterface;
}
  1. In MyProject, define the implementor of that interface:
class SomeInterfaceImpl ISomeInterface
{
    public void DoSomething(object obj)
    {
        ...
    }
}
  1. In MyProject, tell MyProject.GeoTracker what implementation to use:
MyProject.GeoTracker.MyClass1.SomeInterface = new SomeInterfaceImpl();
  1. When MyProject.GeoTracker needs the work done, it does:
MyClass1.SomeInterface?.Invoke(...);

Which does nothing, if that variable has not been set.


EVENTS

An event is often an alternative to the above. Step #2 defines an event; Step #4 does eventHandler = ... to attach an action to perform on that event. This is a fundamental c# concept, that hopefully you have used many times, to get a standard library to execute your code, even though that library doesn't have a reference to your code. Think about that.


DEPENDENCY INJECTION

Everything else you need is elaborations on the above. Its often worth using a Dependency Injection library to manage the details cleanly.


ADDITIONAL NOTES

  • Often, you'll need to define additional interfaces, in order to pass data back and forth. If you want MyProject.GeoTracker to act on a MyProject.MyClass2 instance, define interface MyProject.GeoTracker.IMyClass2 which exposes the needed members. Then public class MyClass2 : IMyClass2.

  • Worst case, you'll need another DLL to hold shared interfaces. So all your projects can refer to them. Personally, I've never encountered a situation that required that.

  • Does this require more coding than if you could directly make a circular refernce?

Yes, but IMHO its worth it: it documents places where you are forcing a "reverse-dependency": making 'lower-level' code "aware" of something in 'higher-level' code.

CodePudding user response:

When I have to summon the dark forces of circular references this is my personal technique. "Your mileage many vary." Here I have a solution with class libraries ClassLibraryA and ClassLibraryB. Before I am through here, they'll successfully and safely reference each other.

namespace ClassLibraryA
{
    public class ClassA
    {
    }
}

and

namespace ClassLibraryB
{
    public class ClassB
    {
    }
}

Solution overview

To bootstrap this scheme, the first Solution build is performed before they attempt to reference each other. As a matter of preference, I use a common path for both. The solution builds just fine. It has no reason to complain so far, right?.

Common path

But nothing stops me from invoking the Browse... from here:

enter image description here

...and so now I add the ClassLibraryA.dll to the ClassLibraryB References and vice versa in order to do this:

using ClassLibraryB;

namespace ClassLibraryA
{
    public class ClassA
    {
        public ClassA()
        {
        }
        public ClassB ClassB { get; set; }
    }
}

and this:

using ClassLibraryA;

namespace ClassLibraryB
{
    public class ClassB
    {
        public ClassB()
        {
        }
        public ClassA ClassA { get; set; }
    }
}

and finally this:

static void Main(string[] args)
{
    var classA = new ClassA();
    classA.ClassB = new ClassB();

    var classB = new ClassB();
    classB.ClassA = new ClassA();

    Console.WriteLine($"{classA.GetType().Name} has {classA.ClassB.GetType().Name}");
    Console.WriteLine($"{classB.GetType().Name} has {classB.ClassA.GetType().Name}");
}

So this is one way to thread the needle that I've used in one flavor or another. I'm not claiming this is some kind of The Answer it's just something that's worked for me.

enter image description here

  • Related