Is it possible to avoid unnecessary injection in parent class? - android

Given the following example:
class CustomView extends View {
#Inject
SomeObject mObject;
#Override
protected void onFinishInflate() {
super.onFinishInflate();
getApplicationComponent().inject(this);
}
}
class SecondaryCustomView extends CustomView {
#Inject
AnotherObject mAnotherObject;
#Override
protected void onFinishInflate() {
super.onFinishInflate();
getApplicationComponent().inject(this);
}
}
Both custom views may be used on a layout independently. The second is just a bit more specialized than the first one.
As you can see, both have fields to inject and both need to call inject(). The problem is that when SecondaryCustomView calls its inject(), Dagger injects an instance of AnotherObject and an instance of SomeObject. After the call to super.onFinishInflate(), it creates a second instance of SomeObject. This is not a problem per se, but we are at least creating unnecessary objects.
Is there a way to avoid this? Some way to tell Dagger that a child class has been injected, so ignore the parent injection?
As an example, the Component looks like this:
#Component(...)
public interface AppComponent {
void inject(CustomView);
void inject(SecondaryCustomView);
}

There's no way to do this in Dagger, but you can do it yourself.
To agree with and expand on your point in the comments:
yes it is needed. Dagger does not inject the child objects if we use injection on parent only. however it injects the parent objects if it is called from the child.
That's correct, and noted in "A note about covariance": Though inject(Foo) can accept an instance of Foo or any of its subclasses, Dagger's a compile-time framework; inject(Foo) will not be generated to inject the fields belonging to arbitrary subclasses of Foo because, well, that's impossible to know at compile time. This can be a little surprising, particularly if your Component has both inject(Foo) and inject(FooSubclass) as you have for CustomView and SecondaryCustomView here: With names injectCustomView and injectSecondaryCustomView it would be obvious that only the former is callable from within Foo.
Aside from simply setting an injectedAlready boolean field as a flag, one technique is to create an overridable method, which does not call its superclass implementation:
class CustomView extends View {
#Inject
SomeObject mObject;
#Override
protected void onFinishInflate() {
injectMe();
super.onFinishInflate();
}
protected void injectMe() {
getApplicationComponent().inject(this); // inject(CustomView);
}
}
class SecondaryCustomView extends CustomView {
#Inject
AnotherObject mAnotherObject;
#Override
protected void onFinishInflate() {
super.onFinishInflate();
// ...
}
/** Despite looking identical, the JVM can call the more-specific overload here. */
#Override protected void injectMe() {
getApplicationComponent().inject(this); // inject(SecondaryCustomView)
}
}
If you're looking for a similar solution for Activity and Fragment classes, you can use dagger.android; the built-in mechanism there uses the runtime type of the class to dynamically fetch the right AndroidInjector from a Map. However, that solution doesn't support View at the moment, so this is as close as you can get for your specific case.

Related

ViewModel granularity with Activities and Fragments

This question is centered around the architecture of an Android Application. When using the LifeCycle component ViewModel, is it best to have one ViewModel per fragment or one ViewModel for the parent activity, to which the Fragments are subscribed to?
It seems unclear to me how to orient something like a Master-Detail fragment-activity relationship without some coupling. For instance, if each Fragment had it's own ViewModel, it is unclear how the Activity should know how to react without coupling (interface, direct functions calls).
As I mentioned in the comments, there is no unique way to accomplish this, but ideally, and very specifically to your Master/Detail flow concern, let's analyze the default provided example:
ItemDetialActivity handles fragment creation and display, FAB and menu actions. Note that there is nothing related to user data, only "system" handles . I, for instance, try to limit activities responsabilities to navigation, and stuff you really can't avoid like menu button handling. Now, ItemListActivity appears to be violating this principle because takes care of displaying the list (Google examples only create confusion -IMHO- between these separation of concerns), I would create a separate fragment that contains the RecyclerView and its adapter.
Now to the nitty gritty. Here is a very high-level skeleton I hope you can make use of. Check it out, implement it, and come back if there are any questions:
public interface BaseView {
LifecycleOwner lifecycleOwner();
/* perform actions that affect a basic screen status, like hide/show progress bars and errors,
animate views, etc. */
}
public class BaseRepo {
// will contain LiveData instances which will postValues()
}
public class FooRepo extends BaseRepo {
/* will contain access to database and networking functions, either by creating instance methods
or enforcing with an interface, it's up to you. */
}
public class BaseModel<P extends BasePresenter> extends ViewModel {
protected final FooRepo fooRepo; // optional, can be on concretes
<T> void subscribe(LiveData<T> liveData, Observer<T> observer) {
liveData.observe(view.lifecycleOwner(), observer);
}
<T> void unsubscribe(LiveData<T> liveData, Observer<T> observer) {
if (liveData != null) {
liveData.removeObserver(observer);
}
}
...
}
public abstract class BasePresenter<M extends BaseModel, V extends BaseView> implements LifecycleObserver {
protected V view;
protected M model;
public void setModel(M model) {
this.model = model;
}
public final void attachView(V view, Lifecycle lifecycle) {
this.view = view;
lifecycle.addObserver(this);
}
public void setPresenter(P presenter) {
this.presenter = presenter;
this.presenter.setModel(this);
}
...
}
public abstract class BaseFragment implements BaseView {
/* generics is highly encouraged here, I've seen examples of both BasePresenter<P>
and BaseView<P> */
protected P presenter;
/* You should bind layers here, or in the concrete class,
either with Dagger, reflection, or some other way */
#Override
public LifecycleOwner lifecycleOwner() {
return this;
}
...
}
Now, for every concrete screen you should create a presenter, model, and fragment that derive from the bases, and perform specifics there. I hope it helps.

Should I create BaseActivity/Presenter and View in Mosby MvP concept?

I'm trying to understand concept of MvP design pattern. I mean, I get it, its quite easy. The main problem is optimal implementation. I tried to make my own BaseActivity, BasePresenter and BaseView just to extract part of a joint from all of my activities, I've done this this way:
BaseActivity
public abstract class BaseActivity<T extends BasePresenter<? extends IBaseView>> extends FragmentActivity implements IBaseView {
protected T presenter;
private ActivityConfig activityConfig;
#Override
final protected void onCreate(Bundle savedInstanceState) {
activityConfig = getConfig();
super.onCreate(savedInstanceState);
presenter = createPresenter();
setContentView();
initLibraries();
prepareView(savedInstanceState);
addFragments();
}
protected abstract ActivityConfig getConfig();
protected abstract T createPresenter();
protected abstract void prepareView(Bundle savedInstanceState);
protected abstract void addFragments();
private void setContentView(){
View root = View.inflate(this, activityConfig.layoutId, null);
setContentView(root);
}
private void initLibraries() {
ButterKnife.bind(this);
Timber.plant(new Timber.DebugTree());
}
#Override
public BaseActivity getCurrentContext() {
return this;
}
#Override
public T getPresenter() {
return presenter;
}
}
BasePresenter
public abstract class BasePresenter<T extends IBaseView> {
public abstract void loadData(boolean refresh);
}
BaseView
public interface IBaseView {
BaseActivity getCurrentContext();
BasePresenter getPresenter();
}
It works fine but I feel like this is bad designed so I want to use Mosby instead. The problem is that all of the tutorials don't touch aspect of base classes, they just use Mosby's ones as base (with is bad I suppose? couse I have to duplicate my code (Butterknife.bind() for example). So can you guys give me some good designed quickstart classes for Mosby MVP or give me some tips how should I divide my project? Thanks!
So I see two possibilities:
You could extend from Mosby's MvpActivity as your base class and add your staff like initView(), initLibraries() etc. So that BaseActivity<P extends BasePresenter<? extends BaseView>> extends MvpActivity<P> implements BaseView. Then MyFooActivity extends BaseActivity<FooPresenter>. So you include Butterknife once in BaseActivity and it should work. However, you might have to duplicate that code like Butterknife.bind()` for Fragments, as Activity and Fragments obviously don't have the same super class. I will show you how to solve that above.
Do the other way around: Integrate Mosby's functionality into your BaseActivity. Mosby is build with the principle of "favor composition over inheritance". So what does this actually mean? Mosby offers a ActivityMvpDelegate. As the name already suggests this delegate does all the work of instantiating Presenter etc. But instead of inheriting from MvpActivity you use this delegate and invoke the corresponding delegate methods. Actually Mosby's MvpActivity is doing exactly that if you have a look at the source code. So instead of extending from Mosby'sMvpActivity you simply use MvpActivityDelegate in your BaseActivity.
So what about duplicating code like Butterknife.bind() i.e. in Activity and Fragment. Well, Mosby can share his code like instantiating Presenter etc. between Activity and Fragment because both use the mosby delegate.
So you could apply the same principle: You could put the shared code into a delegate and call the delegate from both, activity and fragments.
The question is: is it worth i.e. Butterknife.bind() is just one single call. You would also have to make one single call yourDelegate.doSomething() ...
But if you have to reuse "critical code" between activity and fragments then favor composition like Mosby does.
If you know that you are only working with Activites then extending from Mosby's MvpActivity would also be a good option as described in 1. solution.
I just wanted to add to sockeqwe's first answer.
It is perfectly fine to create your own base class where it makes sense. It's also pretty straightforward.
For example, I needed to create a base Fragment with some default behavior. All you need to do is duplicate the base generic type signature and pass it along to the base class.
For example:
public abstract class MyBaseFragment<V extends MvpView, P extends MvpPresenter<V>> extends MvpFragment<V, P>

Dagger 2 - how to inject only to base activity/fragment

I am studying a Dagger 2 from many sources such as this one: http://fernandocejas.com/2015/04/11/tasting-dagger-2-on-android/
but I still haven't found an answer to my question.
I work on quite complex application with tens of fragments and several activities in which I want to use DI (dagger 2). For all of those fragments and activities I have one BaseActivity and one BaseFragment. However, as far as I read and tried, in order to use #Inject in my let's say MainActivity, I have to specify it in Component interface and also invoke getApplicationComponent().inject(this) in onCreate method. When I do this for BaseActivity only, #Inject annotated fields in MainActivity is never injected. And what is even worse, I do not find out about that until that specific part of code is executed and NPE is thrown.
So far it is a deal breaker for me, because this can be source of many crashes. I would need to specify tens of fragments and activities in Component interface and not forget to call inject in each onCreate method.
I would be very glad to hear any solution to this since I would really like to use DI..
code example:
#Singleton
#Component(modules = ApplicationModule.class)
public interface ApplicationComponent {
void inject(BaseActivity baseActivity);
Analytics analytics();
}
public class BaseActivity extends AppCompatActivity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
this.getApplicationComponent().inject(this);
}
}
public class MainActivity extends BaseActivity {
#Inject
Analytics analytics;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
analytics.log("event1"); // THROWS NPE!
}
}
You can not inject properties in your subclass by injecting the super (since dagger2 works at compile time and there is no way to dynamically check subclasses for annotated properties.)
You can move analytics up to the super, then it will be injected there. To inject annotated fields in your subclass you will have to call the injection there again.
You can make an abstract method in your baseclass e.g. inject(App app)where you just handle the injection. That way you can't 'miss' it.
As stated in the official documentation:
While a members-injection method for a type will accept instances of its subtypes, only Inject-annotated members of the parameter type and its supertypes will be injected; members of subtypes will not.
move the
#Inject
Analytics analytics;
to your BaseActivity class, the Analytics object is initialized in the superclass and is inherited by sub-classes automatically, therefor u wouldn't get null any more.
public class MainActivity extends BaseActivity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
analytics.log("event1");
} }

how to inject custom arrayadapter into fragment using roboguice 2.0

I have a fragment that contains a listView. The fragment extends roboFragment. The listView has a custom adapter that sets up different UI elements in the getView method. I want to use #InjectView to get the ui elements. I understand that in order to do this, I need to also create the adapter using guice and not the new operator. So this is what my fragment does:
#Inject
TweetsActivityAdapter tweetsAdapter;
The adapter looks like this:
public class TweetsActivityAdapter extends ArrayAdapter<ITweet> {
#InjectView(R.id.ivProfilePic)
ImageView ivProfilePic;
#InjectView(R.id.tvUserName)
TextView tvUserName;
#InjectView(R.id.tvTweet)
TextView tvTweet;
private final static String tag =
"Debug - com.codepath.upkar.twitterapp.TweetsActivityAdapter";
#Inject
public TweetsActivityAdapter(Context context, List<ITweet> tweets) {
super(context, 0, tweets);
}
I read that I need to configure guice and tell it where to get ITweet from. ITweet is just the interface for Tweet model class.
public interface ITweet {
public User getUser();
public void setUser(User user);
public String getBody();
public long getId();
public long getStrId();
public boolean isFavorited();
public boolean isRetweeted();
}
How do I create a binding for the List? I am currently getting an error:
FATAL EXCEPTION: main
E/AndroidRuntime(18753): java.lang.RuntimeException: Unable to start activity
ComponentInfo{com.bindaas.twitterapp/com.bindaas.twitterapp.activities
.TwitterAppActivity}: com.google.inject.ConfigurationException:
Guice configuration errors:
E/AndroidRuntime(18753): 1) No implementation for
java.util.List<com.bindaas.twitterapp.models.ITweet> was bound.
My module is as follows:
public class MyCustomModule implements Module {
#Override
public void configure(Binder binder) {
binder.bind(ITweet.class).to(Tweet.class);
}
}
There is 2 main problems with your code sample:
1) You told the injector how to create an ITweet instance, not a List<ITweet>.
Also, how do you expect RoboGuice to know what tweets you want in that list ?
You could implement a Provider to do something like that, but that seems a bit too much IMO. A simpler way would be to add a setter to your adapter, and set the data to your adapter after it has been instantiated by RoboGuice.
2) You cannot use #InjectView in a ArrayAdapter
If you look at the code of RoboActivity, you'll see this code in the onContentChanged() handler:
#Override
public void onContentChanged() {
super.onContentChanged();
RoboGuice.getInjector(this).injectViewMembers(this);
eventManager.fire(new OnContentChangedEvent());
}
The injectViewMembers() method is the one doing the magic behind #InjectView.
Sadly this method only accept an Activity or a Fragment.
You might have a look at Butterknife (by Jake Wharton), which is leaner than RoboGuice for views injection.

Activity interceptor

Is there any way in android to intercept activity method calls (just the standart ones, like "onStart. onCreate")?
I have a lot of functionality that must be present in every activity in my app, and (since it uses different types of activities (List, Preferences)) the only way to do it is to create my custom extensions for every activity class, which sucks :(
P.S. I use roboguice, but since Dalvik doesn't support code generation at runtime, I guess it doesn't help much.
P.S.S. I thought about using AspectJ, but it's too much of a hassle since it requires a lot of complications (ant's build.xml and all that junk)
The roboguice 1.1.1 release includes some basic event support for components injected into a context. See http://code.google.com/p/roboguice/wiki/Events for more info.
For Example:
#ContextScoped
public class MyObserver {
void handleOnCreate(#Observes OnCreatedEvent e) {
Log.i("MyTag", "onCreated");
}
}
public class MyActivity extends RoboActivity {
#Inject MyObserver observer; // injecting the component here will cause auto-wiring of the handleOnCreate method in the component.
protected void onCreate(Bundle state) {
super.onCreate(state); /* observer.handleOnCreate() will be invoked here */
}
}
You could delegate all the repetitive work to another class that would be embedded in your other activities. This way you limit the repetitive work to creating this object and calling its onCreate, onDestroy methods.
class MyActivityDelegate {
MyActivityDelegate(Activity a) {}
public void onCreate(Bundle savedInstanceState) {}
public void onDestroy() {}
}
class MyActivity extends ListActivity {
MyActivityDelegate commonStuff;
public MyActivity() {
commonStuff = MyActivityDelegate(this);
}
public onCreate(Bundle savedInstanceState) {
commonStuff.onCreate(savedInstanceState);
// ...
}
}
This minimalises the hassle and factorises all common methods and members of your activities. The other way to do it is to subclasse all the API's XXXActivty classes :(
Take a look at http://code.google.com/p/android-method-interceptor/, it uses Java Proxies.

Categories

Resources