Android - How works MortarScope?

155 Views Asked by At

I'm currently taking in my hands an Android App to make a new version of it. The App has been made without Activities nor Fragments. You know, an App developed by someone passionated by abstraction... In there, the concept of MortarScope is used everywhere but really cannot figure out how it works and what is the purpose of that, as well as Mortar. I know there is the documentation here but a clear explanation would be much appreciated.

1

There are 1 best solutions below

2
On

MortarScope is a Map<String, Object> that can have a parent that it inherits from.

// vague behavior mock-up
public class MortarScope {
     Map<String, Object> services = new LinkedHashMap<>();
     MortarScope parent; 

     Map<String, MortarScope> children = new LinkedHashMap<>();

     public MortarScope(MortarScope parent) {
         this.parent = parent;
     }

     public boolean hasService(String tag) {
         return services.contains(tag);
     }

     @Nullable
     public <T> T getService(String tag) {
         if(services.contains(tag)) { 
             // noinspection unchecked
             return (T)services.get(tag);
         }
         if(parent == null) {
             return null;
         }
         return parent.getService(tag);
     }
}

The tricky thing is that a MortarScope can be placed into a MortarContextWrapper, using mortarScope.createContext(context), which will allow you to obtain services from the MortarScope using getSystemService (apparently only on local-level).

This works because ContextWrappers create a hierarchy, and getSystemService() also does a hierarchical look-up.

class MortarContextWrapper extends ContextWrapper {
  private final MortarScope scope;

  private LayoutInflater inflater;

  public MortarContextWrapper(Context context, MortarScope scope) {
    super(context);
    this.scope = scope;
  }

  @Override public Object getSystemService(String name) {
    if (LAYOUT_INFLATER_SERVICE.equals(name)) {
      if (inflater == null) {
        inflater = LayoutInflater.from(getBaseContext()).cloneInContext(this);
      }
      return inflater;
    }
    return scope.hasService(name) ? scope.getService(name) : super.getSystemService(name);
  }
}

This can make a View's context wrapper store a MortarScope if it is created like this

LayoutInflater.from(mortarScope.createContext(this)).inflate(R.layout.view, parent, false);

This means that when you do something like this:

public class MyService {
    public static MyService get(Context context) {
         // noinspection ResourceType
         return (MyService)context.getSystemService("MY_SERVICE");
    }
}

and

public class MyView extends View {
    ...
    MyService myService = MyService.get(getContext());

Then you can force the getSystemService() to obtain MyService from any level above you through a hierarchical look-up across the contexts (view, activity, application).


The parent retains the child unless explicitly destroyed, so the Activity scope is kept alive by the Application scope, and the View scope is kept alive by the Activity scope, therefore configuration change does not destroy services stored inside a MortarScope.