11

I am trying to store my user Object as a singleton with Dagger 2.

    @Provides
    @Named("me")
    @Singleton
    User provideUser(PrefsUtil prefsUtil, UserDao userDao) {
        int id = prefsUtil.getFromPrefs("me", 0);
        if (id == 0){
            return new User();
        }
        try {
            return userDao.queryForId(id);
        } catch (SQLException e) {
            return new User();
        }
    }

It works fine and injects my classes with User object.

However, after logging in and fetching the user from server and storing it in the place the above method queries it from, it will not take effect because it is a singleton. It will provide me with the null user object. In order for it to take effect you have to quit application and reopen it...

The question is how to update/reinitialize the user object annotated with @Name("me") after the actual data is changed so it injects my other classes with the current user object?

Saeed Entezari
  • 3,685
  • 2
  • 19
  • 40

2 Answers2

10

I'm not going to answer your direct question, but give you an advice how to properly implement the functionality that you need.

You are basically trying to implement some kind of UserManager functionality. But instead of encapsulating this logic in a dedicated class, you attempt to delegate the user management responsibilities to DI framework.

This is an abuse of DI framework and very sloppy path to go.

What you need is just this:

@Provides
@Singleton
UserManager provideUserManager(PrefsUtil prefsUtil, UserDao userDao) {
    return new UserManager(prefUtils, userDao);
}

And expose the required funcitonality in UserManager:

public class UserManager {

    private final PrefsUtil mPrefsUtil;
    private final UserDao mUserDao;

    public UserManager(PrefsUtil prefsUtil, UserDao userDao) {
        mPrefsUtil = prefsUtil;
        mUserDao = userDao;
    }

    public User getCurrentUser() {
        int id = mPrefsUtil.getFromPrefs("me", 0);
        if (id == 0){
            return new User();
        }
        try {
            return mUserDao.queryForId(id);
        } catch (SQLException e) {
            return new User();
        }
    }
}

You can see this and this answers in order to get some additional context about DI framework abuse.

You might also want to read this post: Dependency Injection in Android.

Community
  • 1
  • 1
Vasiliy
  • 16,221
  • 11
  • 71
  • 127
  • Very good answer. Thanks Vasily. One question, if UserManager keeps application context as an instance variable, would it be a risk for memory leak? Context would be needed in cases of certain services in which the instance creation needs context. – RobertoAllende May 26 '21 at 02:02
2

Then it no longer may be annotated with Singleton. You have to create your custom Scope.

Then you take responsibility for the object annotated with your custom scope. As soon as your User has been updated you are getting rid of the previous component that provided User object, i.e. nulling it out. Then you are creating a new component and the next time you ask the component to fetch you the User it will create a new one.

Be aware, that any other provider method in the module, that was annotated with your custom scope, will also return newly created object.

Here's a blog post describing how to do that.

azizbekian
  • 60,783
  • 13
  • 169
  • 249
  • 1
    This answer is incorrect - custom scopes are functionally identical to `@Singleton`. Read this: http://www.techyourchance.com/dagger-2-scopes-demystified/. – Vasiliy Feb 07 '17 at 14:38
  • @Vasiliy, would you please clarify what exactly is incorrect? Because I can clarify why the answer is right. A component may have only one scope, it cannot have multiple scopes. `@Singleton` is just a scope that is shipped with Dagger, it may be called anything, it doesn't have any specific functionality attached to it. Thus, if you create a component with a specific scope, than the object that this component provides would always be the same (i.e. singleton). What you have to do - is to get rid of the component, and create a new one, then a new object would be provided, which is the answer. – azizbekian Feb 07 '17 at 18:48
  • What you say in this comment is close to be correct, but it is not what is stated at the beginning of your answer: "Then it no longer may be annotated with Singleton. You have to create your custom Scope". Why I say "close to be correct"? Re-instantiation of component will "reset" ALL annotated objects provided by that component. Giving this advice without mentioning such a devastating side effect is kind of incomplete IMHO – Vasiliy Feb 07 '17 at 20:11
  • @Vasiliy, "Then it no longer may be annotated with Singleton. You have to create your custom Scope" This is true, because you end up with component dependencies, and as long as the parent component of `UserModule` has a scope `@Singleton`, you can no longer use that scope to annotate provides method in child component `UserModule`, thus you have to create custom scope, I see no incorrectness there. – azizbekian Feb 07 '17 at 20:39
  • @Vasiliy, `Giving this advice without mentioning such a devastating side effect is kind of incomplete IMHO` I accept your point. But that's not the way you should clarify your comment. As you said, it's your humble opinion, which doesn't yet show that the answer is incorrect. Thanks for the suggestion, would edit the answer in order to include the "side effects" too. – azizbekian Feb 07 '17 at 20:42
  • I don't know what you mean by "parent component" and "child component" (subcomponents, components dependencies, other?), but the question does not contain any mention of this. In any case, no personal offense meant, however I do think that from technical point of view your answer is incorrect (though I could use less straightforward language). The question was `how to update/reinitialize the user object annotated with @Name("me")` - to me it is clear that OP wants to re-initialize just a one single object. – Vasiliy Feb 07 '17 at 21:05