0

I'm using Dagger 2 and the Repository pattern in Android and am getting tripped up by which scope I should be using for the repository's dependencies and the tradeoffs of using them.

Generally I create a repository per feature. So if we're talking about the registration feature then I'd create a RegistrationRepository. The RegistrationRepository would have 3 different data sources, RegistrationNetworkSource, RegistrationDiscSource and RegistrationMemorySource. When my Activity makes a request to the RegistrationRepository the repo will create an RxJava observable and return it to the activity. The activity can then subscribe to the observable and await the result. If the activity happens to undergo a configuration change before the observable returns a result then the observable can be cached in a separate class that is scoped to the application life cycle and after the activity is re-created it can grab this cached observable and resubscribe to it. And this is where my confusion starts. If my observable is being cached in a class that is scoped to the application scope does that mean that the 3 repository data sources also need to be scoped to the application scope?

My gut tells me that I should scope them to the Application scope. Doing this would allow each source to perform a long running data fetching task that could continue even if the Activity that the request came from undergoes a configuration change. There'd be one instance per app and they'd always be available for use. That sounds great, but doesn't that end up wasting resources? If registration is the first screen of my app and the user spends the rest of their time on the HomeActivity or some other place, then why should the 3 Registration data sources still be alive?

neonDion
  • 2,278
  • 2
  • 20
  • 39

1 Answers1

1

This question is quite similar to your previous question but it seems like there are some unresolved doubts.

To start, I would recommend you read up about how scopes work in the answers to this question. To summarise, there is nothing magical about scopes, they are just there to help you reason about the lifecycles of the objects created from your Components. The instances of the dependencies you generate from the Components will exist where you maintain a reference to them. Normally you would maintain references to the dependencies injected from a @PerActivity Component inside a single activity. For example, if your @PerActivity CoffeeComponent has a CoffeeModule:

@Provides
@PerActivity
public CoffeeMaker coffeeMaker(HotWater hotWater, Beans beans) {
    return new DefaultCoffeeMaker(hotWater, beans);
}

Then you'd normally expect the instances of CoffeeMaker you obtain to follow the lifecycles of a single Activity. However, if you took one of those CoffeeMaker instances and maintain a reference to it inside the Application class, that instance would exist until the Application was destroyed.

Let's try and apply this to your problem:

If my observable is being cached in a class that is scoped to the application scope does that mean that the 3 repository data sources also need to be scoped to the application scope?

No, the repository data sources can be scoped @PerActivity and you could maintain references to the Observables at the @PerApplication (@Singleton) scope. Other Dagger 2 answers here have talked about using the Holder pattern for this. In short, you would make a singleton class with the ability to store the results of the Observables at the app-scoped level. When you make the request using the RegistrationNetworkSource, you can cause the Holder to subscribe, receive, and cache results. Your Activities can obtain the pending results from the Holder rather than directly subscribing to the Observable from the RegistrationRepository.

Some other issues to consider:

How long are your long-running network requests that you require to survive configuration changes? Consider using something like DownloadManager

Are Loaders not a better fit for your use case than Dagger 2 and Rx-Java Observables? Note from the documentation for Loaders the following:

Loaders persist and cache results across configuration changes to prevent duplicate queries.

Community
  • 1
  • 1
David Rawson
  • 20,912
  • 7
  • 88
  • 124
  • If my data sources were each scoped to the Activity and I created an observable in the activity, then cached it, then the activity underwent a configuration change, the cached observable would have a reference to the original data sources. When the new activity is created it would get completely new instances of the data sources. If I get the old observable from the cache and resubscribe then the observable operates on the old data sources and once that operation finishes those old data sources would be out of scope and eligible for garbage collection, right? – neonDion Apr 03 '17 at 15:03
  • (continued) And then in the recreated activity if I were to kick off the same event that created the old observable, the new observable that was created would now operate using the newly created data sources. So what I was asking is possible, but will tie up more memory than needed if a configuration change were to occur during a long running operation because the original observable would ref. old data sources and after the activity was recreated it would create new data sources so for awhile I'd have old data sources and new data sources in memory until the original operation is done. – neonDion Apr 03 '17 at 15:07
  • @neonDion that sounds right and I don't think the extra memory used while the two instances of your data sources are active would be a concern since it would only occur in the case of a config change during the request. The original way you were proposing was to have all the datasources in memory all the time the app was running. BTW are you sure you wouldn't rather use Loaders or even a Service? – David Rawson Apr 03 '17 at 19:57
  • Services and Loaders are potential solutions however these network calls generally aren't long running (but it's good practice to assume they could be long running enough that the activity could undergo a config. change while the call is in-flight) so using a network layer that's scoped to the application lifecycle is sufficient. – neonDion Apr 03 '17 at 21:02
  • The memory footprint of having the "old" data sources in memory and also creating new data sources isn't necessarily prohibitive, however I think it's an important distinction to note. If you are using a memory source and the activity undergoes a configuration change then the next time you try to use memory source it's going to be a new instance (and presumably empty) so anything that might have been important to have in memory will no longer be there. For that reason it might be best to scope data sources to the application and dereference them as needed. – neonDion Apr 03 '17 at 21:05
  • @neonDion it's kind of hard to answer because there's no code or info about how the app is structured. Do users always have to go through registration? Or is it just one time? If there is a chance for the Retrofit services not to require injection then maybe it is okay to put them in application scope since Dagger will only instantiate them if it is necessary – David Rawson Apr 03 '17 at 21:10
  • Yeah, that's a good question. If we use login as an example, then as soon as the user logs in I would think the login repo and login data sources should be eligible to be freed from memory. Sure, the user could logout and need to login again and if the original repo and sources were around still it would be fine to reuse them, but if re-logging in is rare then I would think that freeing memory after login and then creating new login repos and data sources if needed would be the best way to go. – neonDion Apr 03 '17 at 21:25