why this ObjectGraph is not allowed?

76 views
Skip to first unread message

Mingfai

unread,
Jun 17, 2013, 4:27:25 AM6/17/13
to tran...@googlegroups.com
hi John,

I have simple model with 4 items: A data list, adapter, service and activity. The dependency is shown in the attached diagram, or roughly described as follows:
  • Adapter and Service depends on List 
  • Service depends on Adapter
  • Activity depends on Service and Adapter
It doesn't look like a circular dependency at all, but Transfuse generates code that can't compile.The code is pushed to my hello-transfuse repository as test3.

btw, a related question, does Translate do proxy like Guice that support circular dependency or do simple object graph like Dagger?  

I'm interested to learn more about Transfuse as a IoC container/dependency injector.

regards,
mingfai



Idea note_20130617_160111_02.jpg

Mingfai

unread,
Jun 17, 2013, 4:32:51 AM6/17/13
to tran...@googlegroups.com
I wonder if anything in Transfuse is "implicitly" depends on the Activity and Application because it allows me to inject activity/application to any class? How would Transfuse know which Activity to inject? Let's say my service/adapter is shared by multiple activities. 

This is an odd idea. maybe completely wrong.

John Ericksen

unread,
Jun 17, 2013, 9:06:36 PM6/17/13
to tran...@googlegroups.com
Mingfai,

Sorry for the delay on a response, I had a busy day at work.

Transfuse allows circular dependencies and uses the Virtual Proxy pattern to facilitate this.  Although I don't recommend purposefully writing circular dependencies, it does come in handy keeping code clean.

The issue with the graph you produced is the scoping.  Since Service is a Singleton Transfuse cannot create it with the Activity dependency (Service -> Adapter -> Activity).  It looks like you want your Service class notifying the TestAdapter class.   If you really require Service to be a Singleton I would recommend using the @Observes functionality to update the TestAdapter class in a decoupled way.  I've submitted a pull request to your repository with the changes.

Transfuse will inject the Activity currently associated with the object graph.  If you do not scope an object Transfuse will create a new one each time in your graph.  Transfuse has built into it injections relevant to the scope.  If you are using a @Activity everything including the associated views to properties to extras are automatically mapped and available for injection.  Take a look at the generated Activity classes and you will see what I mean.

I need to add some better validation so people can't get into this spot with scoping.  Any ideas around this?

I think you also uncovered a bug with injecting non-public fields, which I will fix momentarily.

Let me know if you need any clarification.  Thanks.

John

Mingfai

unread,
Jun 17, 2013, 11:28:01 PM6/17/13
to tran...@googlegroups.com

Thanks for your explanation. It's very clear.


On Tue, Jun 18, 2013 at 9:06 AM, John Ericksen <johnc...@gmail.com> wrote:

The issue with the graph you produced is the scoping.  Since Service is a Singleton Transfuse cannot create it with the Activity dependency (Service -> Adapter -> Activity).  It looks like you want your Service class notifying the TestAdapter class.   If you really require Service to be a Singleton I would recommend using the @Observes functionality to update the TestAdapter class in a decoupled way.  I've submitted a pull request to your repository with the changes.


Thanks. I'll fix (the scope of) my model first before I turn to use @Observes.
 
Transfuse will inject the Activity currently associated with the object graph.  If you do not scope an object Transfuse will create a new one each time in your graph.  Transfuse has built into it injections relevant to the scope.  If you are using a @Activity everything including the associated views to properties to extras are automatically mapped and available for injection.  Take a look at the generated Activity classes and you will see what I mean.

I exactly expect create a new one ("prototype" scope) as default. btw, I generally think in the Guice mindset as I've been using it since switched from Spring in 2008, and my first Android project uses RoboGuice, can't live without DI.

So, even if there is no compile error, there is a bug in my code. and my usage will create different Adapter instance to the Service and Activity. 

Another problem is my Adapter isn't suppose to need to inject an Activity, it just need any Context that I suppose will be application.getApplicationContext(). In Dagger, I (am forced to) know preciously what Context/Activity to inject, so I know in that case injecting an Activity or other context makes not much difference. And now Transfuse manages everything so I need to pay attention on the scope and what Context Transfuse can provide. 

I'll spend some time to learn about the scoping and see I can get my model work.
 

I need to add some better validation so people can't get into this spot with scoping.  Any ideas around this?

I think I need more practical experience on Transfuse to comment on this. So far, it seems allowing implicit Activity injection may make things less clear. I wouldn't mind if you mandatory require an @ActivityScope before you allow any Activity be injected. (or even @ActivityScope(activity=MyActivity.class) ) Then a better error message may potentially be used. But I honestly don't know Transfuse good enough yet.

If it is doable (and if it's not done already :-) ), it would be great the scope concept can apply to a Fragment in a ViewPager, that Transfuse will inject the current Fragment magically. 


I think you also uncovered a bug with injecting non-public fields, which I will fix momentarily.

Let me know if you need any clarification.  Thanks.
 
John


Thanks again.

Mingfai

 

John Ericksen

unread,
Jun 18, 2013, 12:03:01 AM6/18/13
to tran...@googlegroups.com
I think in the Guice mindset as well.  I followed a similar progression in my work.  Transfuse is heavily influenced by RoboGuice, Guice and Seam (as well as a number of other DI frameworks).

Transfuse is smart enough to determine the right android injection for you.  This means you can inject Context and, if you are in an Activity, Transfuse will inject the Activity.  In other words, Transfuse maps the sup-types of certain injections like the Activity.  So, it is totally valid and acceptable to write TestAdapter like so:

public class TestAdapter extends ArrayAdapter<TestEntry> {
    @Inject public TestAdapter(Context ctx) {super(ctx, R.layout.test1_textview);}
}

I could see the case for requiring @ActivityScope to inject the activity, but that gets messy too.  Typically the scopes are meant to determine the lifecycle of objects, not necessarily what can be injected into them.  I think at least a bit more documentation is needed on how Transfuse injects and definitely some more validation.  What I am leaning towards to to only allow Activity related injections in prototype and context-scoped beans and their related graph.

As for injecting Fragments, it may be already done, but I would have to see what you are talking about for more detail.  Please share your issues and/or results with this, it sounds like a cool idea.

Thanks.

John

Mingfai

unread,
Jun 18, 2013, 1:54:39 AM6/18/13
to tran...@googlegroups.com
On Tue, Jun 18, 2013 at 12:03 PM, John Ericksen <johnc...@gmail.com> wrote:

I could see the case for requiring @ActivityScope to inject the activity, but that gets messy too.  Typically the scopes are meant to determine the lifecycle of objects, not necessarily what can be injected into them.  I think at least a bit more documentation is needed on how Transfuse injects and definitely some more validation.  What I am leaning towards to to only allow Activity related injections in prototype and context-scoped beans and their related graph.


I don't know too much about annotation processing, but from my experience of using the Transfuse and frameworks/libs by those Square ppl, they are relatively hard to troubleshoot. Quite often, there are compilation errors without any indication of what go wrong. Sometimes, I need to roll back the code and re-do step by step to figure out what went wrong. But for sure, I'll still use these frameworks because user xp > dev xp for now, at least until Android devices are generally powerful enough to allow us to ignore the performance loss in runtime processing.

Is it possible to show an error message telling "you can't inject an Activity to a Singleton-scoped instance" or sth like that? Just curious if it is very hard to implement proper error message in annotation processing.

Mingfai


John Ericksen

unread,
Jun 18, 2013, 10:49:40 AM6/18/13
to tran...@googlegroups.com
Is it possible to show an error message telling "you can't inject an Activity to a Singleton-scoped instance" or sth like that? Just curious if it is very hard to implement proper error message in annotation processing.

It is definitely possible.  The tricky thing is detecting the issue and responding with a useful message.

Mingfai

unread,
Jun 27, 2013, 3:04:49 AM6/27/13
to tran...@googlegroups.com
On Tue, Jun 18, 2013 at 11:28 AM, Mingfai <mingf...@gmail.com> wrote:

Transfuse will inject the Activity currently associated with the object graph.  If you do not scope an object Transfuse will create a new one each time in your graph.  Transfuse has built into it injections relevant to the scope.  If you are using a @Activity everything including the associated views to properties to extras are automatically mapped and available for injection.  Take a look at the generated Activity classes and you will see what I mean.

I exactly expect create a new one ("prototype" scope) as default. btw, I generally think in the Guice mindset as I've been using it since switched from Spring in 2008, and my first Android project uses RoboGuice, can't live without DI.

So, even if there is no compile error, there is a bug in my code. and my usage will create different Adapter instance to the Service and Activity. 

Another problem is my Adapter isn't suppose to need to inject an Activity, it just need any Context that I suppose will be application.getApplicationContext(). In Dagger, I (am forced to) know preciously what Context/Activity to inject, so I know in that case injecting an Activity or other context makes not much difference. And now Transfuse manages everything so I need to pay attention on the scope and what Context Transfuse can provide. 

I'll spend some time to learn about the scoping and see I can get my model work.
 

I'm back to look at the scope issue. 

When everything is in the "activity-scope", injecting Activity, Application and Context are just fine. I understand in singleton-scope, it is not suppose to support injecting an Activity. However, should Context and Application be inject-able to any singleton? (Context becomes Application context)

Take an example:
@Activity class MyActivity{
   @Inject MySingleton singleton;
}

@Singleton class MySingleton{
   final Context ctx; 
   final Application app; 
   @Inject public MySingleton(Context context, Application application){
       this.ctx = context;
       this.app = application;
   }
}

Both ctx and app are null. And I expect they can be injected. What do you think?

regards,
mingfai


 

Mingfai

unread,
Jun 27, 2013, 3:11:40 AM6/27/13
to tran...@googlegroups.com
nvm. please disregard. It just work expected. 

Mingfai

unread,
Jun 27, 2013, 3:19:01 AM6/27/13
to tran...@googlegroups.com
for now, constructor injection works as expected. field injection and injection to module @Provides method doesn't work.

Mingfai

unread,
Jun 27, 2013, 4:05:06 AM6/27/13
to tran...@googlegroups.com
The work or doesn't work comments above may be specific to my environment. (for any problem report, i tested in hello-transfuse) I'm not too sure how exactly Transfuse work in allowing Context and Application inject to constructor, field and module methods yet. Sometimes it can't compile and sometimes it can. 

However, for Application, the Factory approach you mentioned earlier seems to work very well. I created an ApplicationFactory as follows:
@Factory public interface ApplicationFactory {
    Application getApplication();
}

And the application can be loaded in anywhere with:
Factories.get(ApplicationFactory.class).getApplication();

Thanks,
mingfai

John Ericksen

unread,
Jun 27, 2013, 12:05:01 PM6/27/13
to tran...@googlegroups.com
This is troubling.  Constructor, Method and Field injection should work nearly the same.  Can you share some code to show off the problem with @Provides?

I am aware of a bug with Singleton scoped classes and injecting Context related objects.  Specifically, "this" is used when referencing the current context in Singleton scope providers, when a call to the Scopes object is more appropriate.  I am actively working this issue and should have a resolution soon.  Right now the best thing to do is either avoid it or pass the needed Application / Context in through the method call where it is needed:

@Singleton class MySingleton{
 
    public void doSomethingWithContext(Context context){...}
}

I think the target here is to support the following use case in singleton scopes:

@Singleton class MySingleton{
   final Provider<Context> ctxProvider;
   final Provider<Application> appProvider;
   @Inject public MySingleton(Provider<Context> ctxProvider, Provider<Application> ctxProvider){
       this.ctxProvider = ctxProvider;
       this.ctxProvider = ctxProvider;
   }
}

then call ctxProvider.get() whenever you need the current context.

The challenge with this approach is to determine what is the current context.  There are a number of instances where the android contexts overlap and it is ambiguous what context you are in at a given moment.  I remember we came across this same issue in RoboGuice when developing the EventManager/@Observes functionality.

I was playing around with some validation around contexts and it is very difficult to tell what an error is.  For instance, you may want a prototype scoped object to be injected into your Singleton scoped object.  This ends up making the Prototype scoped object essentially a Singleton as well.  Should this be an error?  I don't think so.  So for Context-Scoped objects (Resources, Preferences, Application?) they can be injected as well into singletons, but I think it is up to the developer to determine what the object graph should look like.

I need to think about this further and maybe see what other IOC frameworks do here.  Any insights from you are welcome of course.

Scoping is tough.

John
Reply all
Reply to author
Forward
0 new messages