Scope — Dependency Injection

Aaron Harris
4 min readFeb 12, 2020

Scope is (IMO) the most complicated topic of modern Dependency Injection (DI) frameworks. Let me attempt to explain..

For more insight about how scoping might help you, please read Compartmentalizing Code

TLDR; scroll down to Examples

Let’s say all the blue boxes are Fragments or custom Views.

What is scope and how does scoping work?

I’ll start by giving some comparisons between an AppSingleton and an ActivitySingleton then give some example scopes related to the above photo.

Scope is the same term you’d use in code. A variable defined in a scope will get garbage collected / reclaimed once it falls out of scope.

Singletons by definition can only have one instance — the implicit scope for any Singleton is “The entire application” and is synonymous with being an “App Scoped Singleton”. An “Activity Scoped Singleton” can only have one instance per Activity.

Let us evaluate a traditional Singleton (AppSingleton)

PROs

  • Accessible: A place you can put logic and data and you can access it from anywhere in the app. This enables you to pull code out of the detail implementations and put them into more shareable places.
  • Testable: Adopt many small singletons and you’ve got easy to test controllers that manage the state of your application.
  • Decoupling: Shared memory means two independent parts can communicate through an abstract PubSub channel. NICE

CONs

  • Lives forever: Memory that goes in is global. Any means of reclaiming memory will need to be a clumsy manual process that risks…
  • Race conditions: It is up to you to lock down your code. We’re not talking about concurrency here, we’re talking about lifecycle and timing. Let’s say two Activities are sharing a Singleton. MyDetailActivity.onCreate() writes some state, then MyMainActivity.onDestroy() writes some state after. From the perspective of MyDetailActivity it may be easy to miss that MyMainActivity had the final say.

Enter ActivitySingleton

There are other scopes than App and Activity, such as Fragment or View — but for the sake of simplicity let’s keep it simple. The deeper scopes follow the same pattern so understanding ActivityScope will give you a clear picture of the others.

PROs (vs AppSingleton)

  • Memory sharing is limited to components that live under this Activity instance. So that means, any activity aware component can safely inject an ActivitySingleton without fear of conflict from another Activity. For example, nothing MyDetailActivity does should directly impact MyMainActivity, that would be bad.
  • Memory is garbage collected once the Activity is destroyed. This is great because it’s no longer necessary, it just goes away. We can now build things associated with the context that are reclaimed with the context.
  • Decoupling that is more specific. Given that MyMainActivity and MyDetailActivity should never communicate, using an AppSingleton as a PubSub channel can get dicey since they’re both using it — how do we know from which context the message originated? How do we know which context it’s destined for? With an ActivitySingleton, only components in “our” activity will get this ActivitySingleton, so one context to worry about.
  • Race Conditions reduced but not gone. No more activities overwriting each other, but the problem can still apply to Fragments or Views overwriting each other. Consider a FragmentSingleton or ViewSingleton… ahh now it’s clicking.

Examples

Let me link that photo again so you don’t have to scroll.

Let’s say all the blue boxes are Fragments or custom Views.

Some facts about the above:

MyApplication is visible to every component within the Application. Since it is available anywhere, AppSingletons which are App Scoped are available anywhere, always, global, be careful.

MyMainActivity is visible to all the blue boxes on the left. This means all the blue boxes on the left will receive the same instance of any injected ActivitySingleton.

MyDetailActivity is visible to all the blue boxes on the right. This means all the blue boxes on the right will receive the same instance of any injected ActivitySingleton.

Imagine [ContentViewPager] and [UserComments] both live under MyMainActivity and inject MyEventSystem which is an ActivitySingleton. They will both receive the same instance and can use that instance to share events in a decoupled way. See Compartmentalizing Code.

Now let’s say MyMainActivity is shutting down and MyDetailActivity is starting up. Imagine [LargeContent] and [ContentDetails] both live under MyDetailActivity and inject the same MyEventSystem. They will not receive the same instance of the MyEventSystem. Even though they are all Activity-Scoped. An ActivitySingleton is only shared between components of the same Activity. So MyMainActivity and its children get their own MyEventSystem. MyDetailActivity and its children get their own MyEventSystem. Now the code is structured in such a way that future developers don’t need to worry about cross-talk. Beautiful.

Any events registered in MyEventSystem provided to MyMainActivity and its children will be garbage collected when MyMainActivity is destroyed. The memory does not live forever like an AppSingleton. This is great for keeping a low footprint. If you want app-wide memory, use an AppSingleton. This allows you to be selective.

Real Quick — FragmentSingleton

Imagine the same scenario as Activities applied to Fragments. Two Fragments on two sides of the screen can still share ActivitySingletons and AppSingletons, however each Fragment will receive its own instance of FragmentSingletons. This allows you to drill down further and further with refining memory usage and scoping communication.

A real life example. Consider a ViewPager. It lives under one activity, but really all the contents on the screen are neatly packaged under each View in the ViewPager. Does that mean all the Singletons across all the Views in the ViewPager must be the same, shared? No, thats the beauty of a FragmentSingleton or a ViewSingleton. Establish the root view within each page of that ViewPager as the defining scope and now each ViewPager “page” gets its own View/Fragment Singletons allowing truly modular development.

*Tear*

--

--