Git Product home page Git Product logo

mortar's Introduction

Mortar

What's a Mortar?

Mortar provides a simplified, composable overlay for the Android lifecycle, to aid in the use of Views as the modular unit of Android applications. It leverages Context#getSystemService to act as an a la carte supplier of services like dependency injection, bundle persistence, and whatever else your app needs to provide itself.

One of the most useful services Mortar can provide is its
BundleService, which gives any View (or any object with access to the Activity context) safe access to the Activity lifecycle's persistence bundle. For fans of the Model View Presenter pattern, we provide a persisted Presenter class that builds on BundleService. Presenters are completely isolated from View concerns. They're particularly good at surviving configuration changes, weathering the storm as Android destroys your portrait Activity and Views and replaces them with landscape doppelgangers.

Mortar can similarly make Dagger ObjectGraphs (or Dagger2 Components) visible as system services. Or not โ€” these services are completely decoupled.

Everything is managed by MortarScope singletons, typically backing the top level Application and Activity contexts. You can also spawn your own shorter lived scopes to manage transient sessions, like the state of an object being built by a set of wizard screens.

These nested scopes can shadow the services provided by higher level scopes. For example, a Dagger extension graph specific to your wizard session can cover the one normally available, transparently to the wizard Views โ€” they can make calls like ObjectGraphService.inject(getContext(), this) without considering which graph will do the injection.

The Big Picture

An application will typically have a singleton MortarScope instance. Its job is to serve as a delegate to the app's getSystemService method, something like:

public class MyApplication extends Application {
  private MortarScope rootScope;

  @Override public Object getSystemService(String name) {
    if (rootScope == null) rootScope = MortarScope.buildRootScope().build();

    return rootScope.hasService(name) ? rootScope.getService(name) : super.getSystemService(name);
  }
}

This exposes a single, core service, the scope itself. From the scope you can spawn child scopes, and you can register objects that implement the [Scoped][scoped] with it for setup and tear-down calls.

  • Scoped#onEnterScope(MortarScope)
  • Scoped#onExitScope(MortarScope)

To make a scope provide other services, like a Dagger ObjectGraph, you register them while building the scope. That would make our Application's getSystemService method look like this:

  @Override public Object getSystemService(String name) {
    if (rootScope == null) {
      rootScope = MortarScope.buildRootScope()
        .with(ObjectGraphService.SERVICE_NAME, ObjectGraph.create(new RootModule()))
        .build();
    }

    return rootScope.hasService(name) ? rootScope.getService(name) : super.getSystemService(name);
  }

Now any part of our app that has access to a Context can inject itself:

public class MyView extends LinearLayout {
  @Inject SomeService service;

  public MyView(Context context, AttributeSet attrs) {
    super(context, attrs);
    ObjectGraphService.inject(context, this);
  }
}

To take advantage of the BundleService describe above, you'll put similar code into your Activity. If it doesn't exist already, you'll build a sub-scope to back the Activity's getSystemService method, and while building it set up the BundleServiceRunner. You'll also notify the BundleServiceRunner each time onCreate and onSaveInstanceState are called, to make the persistence bundle available to the rest of the app.

public class MyActivity extends Activity {
  private MortarScope activityScope;

  @Override public Object getSystemService(String name) {
    MortarScope activityScope = MortarScope.findChild(getApplicationContext(), getScopeName());

    if (activityScope == null) {
      activityScope = MortarScope.buildChild(getApplicationContext(), getScopeName()) //
          .withService(BundleServiceRunner.SERVICE_NAME, new BundleServiceRunner())
          .withService(HelloPresenter.class.getName(), new HelloPresenter())
          .build();
    }

    return activityScope.hasService(name) ? activityScope.getService(name)
        : super.getSystemService(name);
  }

  @Override protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    BundleServiceRunner.getBundleServiceRunner(this).onCreate(savedInstanceState);
    setContentView(R.layout.main_view);
  }

  @Override protected void onSaveInstanceState(Bundle outState) {
    super.onSaveInstanceState(outState);
    BundleServiceRunner.getBundleServiceRunner(this).onSaveInstanceState(outState);
  }
}

With that in place, any object in your app can sign up with the BundleService to save and restore its state. This is nice for views, since Bundles are less of a hassle than the Parcelables required by View#onSaveInstanceState, and a boon to any business objects in the rest of your app.

Download

Download [the latest JAR][jar] or grab via Maven:

<dependency>
    <groupId>com.squareup.mortar</groupId>
    <artifactId>mortar</artifactId>
    <version>(insert latest version)</version>
</dependency>

or Gradle:

compile 'com.squareup.mortar:mortar:(latest version)'

Full Disclosure

This stuff has been in "rapid" development over a pretty long gestation period, but is finally stabilizing. We don't expect drastic changes before cutting a 1.0 release, but we still cannot promise a stable API from release to release.

Mortar is a key component of multiple Square apps, including our flagship Square Register app.

License

Copyright 2013 Square, Inc.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

   http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

mortar's People

Contributors

andrewreitz avatar dnkoutso avatar holmes avatar jakewharton avatar jdreesen avatar loganj avatar pyricau avatar rjrjr avatar

Watchers

 avatar  avatar  avatar

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.